diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7dd65babf608..913ed2e81b63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -740,6 +740,7 @@ jobs: REDSHIFT_IAM_ROLES: ${{ vars.REDSHIFT_IAM_ROLES }} REDSHIFT_VPC_SECURITY_GROUP_IDS: ${{ vars.REDSHIFT_VPC_SECURITY_GROUP_IDS }} REDSHIFT_S3_TPCH_TABLES_ROOT: ${{ vars.REDSHIFT_S3_TPCH_TABLES_ROOT }} + REDSHIFT_S3_UNLOAD_ROOT: ${{ vars.REDSHIFT_S3_UNLOAD_ROOT }} if: >- contains(matrix.modules, 'trino-redshift') && (contains(matrix.profile, 'cloud-tests') || contains(matrix.profile, 'fte-tests')) && @@ -752,6 +753,7 @@ jobs: -Dtest.redshift.jdbc.password="${REDSHIFT_PASSWORD}" \ -Dtest.redshift.jdbc.endpoint="${REDSHIFT_ENDPOINT}:${REDSHIFT_PORT}/" \ -Dtest.redshift.s3.tpch.tables.root="${REDSHIFT_S3_TPCH_TABLES_ROOT}" \ + -Dtest.redshift.s3.unload.root="${REDSHIFT_S3_UNLOAD_ROOT}" \ -Dtest.redshift.iam.role="${REDSHIFT_IAM_ROLES}" \ -Dtest.redshift.aws.region="${AWS_REGION}" \ -Dtest.redshift.aws.access-key="${AWS_ACCESS_KEY_ID}" \ @@ -878,7 +880,6 @@ jobs: - suite-7-non-generic - suite-hive-transactional - suite-azure - - suite-delta-lake-databricks91 - suite-delta-lake-databricks104 - suite-delta-lake-databricks113 - suite-delta-lake-databricks122 @@ -919,9 +920,6 @@ jobs: ignore exclusion if: >- ${{ env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || secrets.GCP_CREDENTIALS_KEY != '' }} - - suite: suite-delta-lake-databricks91 - ignore exclusion if: >- - ${{ env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || secrets.DATABRICKS_TOKEN != '' }} - suite: suite-delta-lake-databricks104 ignore exclusion if: >- ${{ env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || secrets.DATABRICKS_TOKEN != '' }} @@ -989,7 +987,6 @@ jobs: AWS_REGION: "" TRINO_AWS_ACCESS_KEY_ID: "" TRINO_AWS_SECRET_ACCESS_KEY: "" - DATABRICKS_91_JDBC_URL: "" DATABRICKS_104_JDBC_URL: "" DATABRICKS_113_JDBC_URL: "" DATABRICKS_122_JDBC_URL: "" @@ -1067,7 +1064,6 @@ jobs: AWS_REGION: ${{ vars.TRINO_AWS_REGION }} TRINO_AWS_ACCESS_KEY_ID: ${{ vars.TRINO_AWS_ACCESS_KEY_ID }} TRINO_AWS_SECRET_ACCESS_KEY: ${{ secrets.TRINO_AWS_SECRET_ACCESS_KEY }} - DATABRICKS_91_JDBC_URL: ${{ vars.DATABRICKS_91_JDBC_URL }} DATABRICKS_104_JDBC_URL: ${{ vars.DATABRICKS_104_JDBC_URL }} DATABRICKS_113_JDBC_URL: ${{ vars.DATABRICKS_113_JDBC_URL }} DATABRICKS_122_JDBC_URL: ${{ vars.DATABRICKS_122_JDBC_URL }} diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index 94878e86e7d2..d5948c669b92 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/client/trino-client/pom.xml b/client/trino-client/pom.xml index 5e663b0af009..3b37b0fc7aef 100644 --- a/client/trino-client/pom.xml +++ b/client/trino-client/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/client/trino-client/src/main/java/io/trino/client/StatementStats.java b/client/trino-client/src/main/java/io/trino/client/StatementStats.java index b0d3e94ef929..d4a68eb62788 100644 --- a/client/trino-client/src/main/java/io/trino/client/StatementStats.java +++ b/client/trino-client/src/main/java/io/trino/client/StatementStats.java @@ -36,14 +36,19 @@ public class StatementStats private final int queuedSplits; private final int runningSplits; private final int completedSplits; + private final long planningTimeMillis; + private final long analysisTimeMillis; private final long cpuTimeMillis; private final long wallTimeMillis; private final long queuedTimeMillis; private final long elapsedTimeMillis; + private final long finishingTimeMillis; + private final long physicalInputTimeMillis; private final long processedRows; private final long processedBytes; private final long physicalInputBytes; private final long physicalWrittenBytes; + private final long internalNetworkInputBytes; private final long peakMemoryBytes; private final long spilledBytes; private final StageStats rootStage; @@ -60,14 +65,19 @@ public StatementStats( @JsonProperty("queuedSplits") int queuedSplits, @JsonProperty("runningSplits") int runningSplits, @JsonProperty("completedSplits") int completedSplits, + @JsonProperty("planningTimeMillis") long planningTimeMillis, + @JsonProperty("analysisTimeMillis") long analysisTimeMillis, @JsonProperty("cpuTimeMillis") long cpuTimeMillis, @JsonProperty("wallTimeMillis") long wallTimeMillis, @JsonProperty("queuedTimeMillis") long queuedTimeMillis, @JsonProperty("elapsedTimeMillis") long elapsedTimeMillis, + @JsonProperty("finishingTimeMillis") long finishingTimeMillis, + @JsonProperty("physicalInputTimeMillis") long physicalInputTimeMillis, @JsonProperty("processedRows") long processedRows, @JsonProperty("processedBytes") long processedBytes, @JsonProperty("physicalInputBytes") long physicalInputBytes, @JsonProperty("physicalWrittenBytes") long physicalWrittenBytes, + @JsonProperty("internalNetworkInputBytes") long internalNetworkInputBytes, @JsonProperty("peakMemoryBytes") long peakMemoryBytes, @JsonProperty("spilledBytes") long spilledBytes, @JsonProperty("rootStage") StageStats rootStage) @@ -82,14 +92,19 @@ public StatementStats( this.queuedSplits = queuedSplits; this.runningSplits = runningSplits; this.completedSplits = completedSplits; + this.planningTimeMillis = planningTimeMillis; + this.analysisTimeMillis = analysisTimeMillis; this.cpuTimeMillis = cpuTimeMillis; this.wallTimeMillis = wallTimeMillis; this.queuedTimeMillis = queuedTimeMillis; this.elapsedTimeMillis = elapsedTimeMillis; + this.finishingTimeMillis = finishingTimeMillis; + this.physicalInputTimeMillis = physicalInputTimeMillis; this.processedRows = processedRows; this.processedBytes = processedBytes; this.physicalInputBytes = physicalInputBytes; this.physicalWrittenBytes = physicalWrittenBytes; + this.internalNetworkInputBytes = internalNetworkInputBytes; this.peakMemoryBytes = peakMemoryBytes; this.spilledBytes = spilledBytes; this.rootStage = rootStage; @@ -155,6 +170,18 @@ public int getCompletedSplits() return completedSplits; } + @JsonProperty + public long getPlanningTimeMillis() + { + return planningTimeMillis; + } + + @JsonProperty + public long getAnalysisTimeMillis() + { + return analysisTimeMillis; + } + @JsonProperty public long getCpuTimeMillis() { @@ -179,6 +206,18 @@ public long getElapsedTimeMillis() return elapsedTimeMillis; } + @JsonProperty + public long getFinishingTimeMillis() + { + return finishingTimeMillis; + } + + @JsonProperty + public long getPhysicalInputTimeMillis() + { + return physicalInputTimeMillis; + } + @JsonProperty public long getProcessedRows() { @@ -203,6 +242,12 @@ public long getPhysicalWrittenBytes() return physicalWrittenBytes; } + @JsonProperty + public long getInternalNetworkInputBytes() + { + return internalNetworkInputBytes; + } + @JsonProperty public long getPeakMemoryBytes() { @@ -236,14 +281,19 @@ public String toString() .add("queuedSplits", queuedSplits) .add("runningSplits", runningSplits) .add("completedSplits", completedSplits) + .add("planningTimeMillis", planningTimeMillis) + .add("analysisTimeMillis", analysisTimeMillis) .add("cpuTimeMillis", cpuTimeMillis) .add("wallTimeMillis", wallTimeMillis) .add("queuedTimeMillis", queuedTimeMillis) .add("elapsedTimeMillis", elapsedTimeMillis) + .add("finishingTimeMillis", finishingTimeMillis) + .add("physicalInputTimeMillis", physicalInputTimeMillis) .add("processedRows", processedRows) .add("processedBytes", processedBytes) .add("physicalInputBytes", physicalInputBytes) .add("physicalWrittenBytes", physicalWrittenBytes) + .add("internalNetworkInputBytes", internalNetworkInputBytes) .add("peakMemoryBytes", peakMemoryBytes) .add("spilledBytes", spilledBytes) .add("rootStage", rootStage) @@ -267,14 +317,19 @@ public static class Builder private int queuedSplits; private int runningSplits; private int completedSplits; + private long planningTimeMillis; + private long analysisTimeMillis; private long cpuTimeMillis; private long wallTimeMillis; private long queuedTimeMillis; private long elapsedTimeMillis; + private long finishingTimeMillis; + private long physicalInputTimeMillis; private long processedRows; private long processedBytes; private long physicalInputBytes; private long physicalWrittenBytes; + private long internalNetworkInputBytes; private long peakMemoryBytes; private long spilledBytes; private StageStats rootStage; @@ -341,6 +396,18 @@ public Builder setCompletedSplits(int completedSplits) return this; } + public Builder setPlanningTimeMillis(long planningTimeMillis) + { + this.planningTimeMillis = planningTimeMillis; + return this; + } + + public Builder setAnalysisTimeMillis(long analysisTimeMillis) + { + this.analysisTimeMillis = analysisTimeMillis; + return this; + } + public Builder setCpuTimeMillis(long cpuTimeMillis) { this.cpuTimeMillis = cpuTimeMillis; @@ -365,6 +432,18 @@ public Builder setElapsedTimeMillis(long elapsedTimeMillis) return this; } + public Builder setFinishingTimeMillis(long finishingTimeMillis) + { + this.finishingTimeMillis = finishingTimeMillis; + return this; + } + + public Builder setPhysicalInputTimeMillis(long physicalInputTimeMillis) + { + this.physicalInputTimeMillis = physicalInputTimeMillis; + return this; + } + public Builder setProcessedRows(long processedRows) { this.processedRows = processedRows; @@ -389,6 +468,12 @@ public Builder setPhysicalWrittenBytes(long physicalWrittenBytes) return this; } + public Builder setInternalNetworkInputBytes(long internalNetworkInputBytes) + { + this.internalNetworkInputBytes = internalNetworkInputBytes; + return this; + } + public Builder setPeakMemoryBytes(long peakMemoryBytes) { this.peakMemoryBytes = peakMemoryBytes; @@ -420,14 +505,19 @@ public StatementStats build() queuedSplits, runningSplits, completedSplits, + planningTimeMillis, + analysisTimeMillis, cpuTimeMillis, wallTimeMillis, queuedTimeMillis, elapsedTimeMillis, + finishingTimeMillis, + physicalInputTimeMillis, processedRows, processedBytes, physicalInputBytes, physicalWrittenBytes, + internalNetworkInputBytes, peakMemoryBytes, spilledBytes, rootStage); diff --git a/client/trino-client/src/main/java/io/trino/client/uri/HttpClientFactory.java b/client/trino-client/src/main/java/io/trino/client/uri/HttpClientFactory.java index 1266f5a2307c..796a42b40605 100644 --- a/client/trino-client/src/main/java/io/trino/client/uri/HttpClientFactory.java +++ b/client/trino-client/src/main/java/io/trino/client/uri/HttpClientFactory.java @@ -54,10 +54,6 @@ public static OkHttpClient.Builder toHttpClientBuilder(TrinoUri uri, String user OkHttpClient.Builder builder = unauthenticatedClientBuilder(uri, userAgent); setupCookieJar(builder); - if (!uri.isUseSecureConnection()) { - setupInsecureSsl(builder); - } - if (uri.hasPassword()) { if (!uri.isUseSecureConnection()) { throw new RuntimeException("TLS/SSL is required for authentication with username and password"); @@ -65,34 +61,6 @@ public static OkHttpClient.Builder toHttpClientBuilder(TrinoUri uri, String user builder.addNetworkInterceptor(basicAuth(uri.getRequiredUser(), uri.getPassword().orElseThrow(() -> new RuntimeException("Password expected")))); } - if (uri.isUseSecureConnection()) { - ConnectionProperties.SslVerificationMode sslVerificationMode = uri.getSslVerification(); - if (sslVerificationMode.equals(FULL) || sslVerificationMode.equals(CA)) { - setupSsl( - builder, - uri.getSslKeyStorePath(), - uri.getSslKeyStorePassword(), - uri.getSslKeyStoreType(), - uri.getSslUseSystemKeyStore(), - uri.getSslTrustStorePath(), - uri.getSslTrustStorePassword(), - uri.getSslTrustStoreType(), - uri.getSslUseSystemTrustStore()); - } - if (sslVerificationMode.equals(FULL)) { - uri.getHostnameInCertificate().ifPresent(certHostname -> - setupAlternateHostnameVerification(builder, certHostname)); - } - - if (sslVerificationMode.equals(CA)) { - builder.hostnameVerifier((hostname, session) -> true); - } - - if (sslVerificationMode.equals(NONE)) { - setupInsecureSsl(builder); - } - } - if (uri.getKerberosRemoteServiceName().isPresent()) { if (!uri.isUseSecureConnection()) { throw new RuntimeException("TLS/SSL is required for Kerberos authentication"); @@ -145,7 +113,6 @@ public static OkHttpClient.Builder toHttpClientBuilder(TrinoUri uri, String user builder.addNetworkInterceptor(authenticator); } - uri.getDnsResolver().ifPresent(resolverClass -> builder.dns(instantiateDnsResolver(resolverClass, uri.getDnsResolverContext())::lookup)); return builder; } @@ -157,6 +124,40 @@ public static OkHttpClient.Builder unauthenticatedClientBuilder(TrinoUri uri, St setupHttpProxy(builder, uri.getHttpProxy()); setupTimeouts(builder, toIntExact(uri.getTimeout().toMillis()), TimeUnit.MILLISECONDS); setupHttpLogging(builder, uri.getHttpLoggingLevel()); + + if (uri.isUseSecureConnection()) { + ConnectionProperties.SslVerificationMode sslVerificationMode = uri.getSslVerification(); + if (sslVerificationMode.equals(FULL) || sslVerificationMode.equals(CA)) { + setupSsl( + builder, + uri.getSslKeyStorePath(), + uri.getSslKeyStorePassword(), + uri.getSslKeyStoreType(), + uri.getSslUseSystemKeyStore(), + uri.getSslTrustStorePath(), + uri.getSslTrustStorePassword(), + uri.getSslTrustStoreType(), + uri.getSslUseSystemTrustStore()); + } + if (sslVerificationMode.equals(FULL)) { + uri.getHostnameInCertificate().ifPresent(certHostname -> + setupAlternateHostnameVerification(builder, certHostname)); + } + + if (sslVerificationMode.equals(CA)) { + builder.hostnameVerifier((hostname, session) -> true); + } + + if (sslVerificationMode.equals(NONE)) { + setupInsecureSsl(builder); + } + } + else { + setupInsecureSsl(builder); + } + + uri.getDnsResolver().ifPresent(resolverClass -> builder.dns(instantiateDnsResolver(resolverClass, uri.getDnsResolverContext())::lookup)); + return builder; } 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 ab72f2db0278..3ea7a940e665 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 @@ -143,7 +143,7 @@ private String newQueryResults(String state) TypedQueryData.of(IntStream.range(0, numRecords) .mapToObj(index -> Stream.of((Object) index, "a").collect(toList())) .collect(toList())), - new StatementStats(state, state.equals("QUEUED"), true, OptionalDouble.of(0), OptionalDouble.of(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null), + new StatementStats(state, state.equals("QUEUED"), true, OptionalDouble.of(0), OptionalDouble.of(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null), null, ImmutableList.of(), null, diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index 19b74cc7804d..1f6688a2fdeb 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/client/trino-jdbc/src/main/java/io/trino/jdbc/QueryStats.java b/client/trino-jdbc/src/main/java/io/trino/jdbc/QueryStats.java index d09f3297de66..9acea4fce352 100644 --- a/client/trino-jdbc/src/main/java/io/trino/jdbc/QueryStats.java +++ b/client/trino-jdbc/src/main/java/io/trino/jdbc/QueryStats.java @@ -32,13 +32,20 @@ public final class QueryStats private final int queuedSplits; private final int runningSplits; private final int completedSplits; + private final long planningTimeMillis; + private final long analysisTimeMillis; private final long cpuTimeMillis; private final long wallTimeMillis; private final long queuedTimeMillis; private final long elapsedTimeMillis; + private final long finishingTimeMillis; + private final long physicalInputTimeMillis; private final long processedRows; private final long processedBytes; private final long peakMemoryBytes; + private final long physicalInputBytes; + private final long physicalWrittenBytes; + private final long internalNetworkInputBytes; private final Optional rootStage; public QueryStats( @@ -52,13 +59,20 @@ public QueryStats( int queuedSplits, int runningSplits, int completedSplits, + long planningTimeMillis, + long analysisTimeMillis, long cpuTimeMillis, long wallTimeMillis, long queuedTimeMillis, long elapsedTimeMillis, + long finishingTimeMillis, + long physicalInputTimeMillis, long processedRows, long processedBytes, long peakMemoryBytes, + long physicalInputBytes, + long physicalWrittenBytes, + long internalNetworkInputBytes, Optional rootStage) { this.queryId = requireNonNull(queryId, "queryId is null"); @@ -71,13 +85,20 @@ public QueryStats( this.queuedSplits = queuedSplits; this.runningSplits = runningSplits; this.completedSplits = completedSplits; + this.planningTimeMillis = planningTimeMillis; + this.analysisTimeMillis = analysisTimeMillis; this.cpuTimeMillis = cpuTimeMillis; this.wallTimeMillis = wallTimeMillis; this.queuedTimeMillis = queuedTimeMillis; this.elapsedTimeMillis = elapsedTimeMillis; + this.finishingTimeMillis = finishingTimeMillis; + this.physicalInputTimeMillis = physicalInputTimeMillis; this.processedRows = processedRows; this.processedBytes = processedBytes; this.peakMemoryBytes = peakMemoryBytes; + this.physicalInputBytes = physicalInputBytes; + this.physicalWrittenBytes = physicalWrittenBytes; + this.internalNetworkInputBytes = internalNetworkInputBytes; this.rootStage = requireNonNull(rootStage, "rootStage is null"); } @@ -94,13 +115,20 @@ static QueryStats create(String queryId, StatementStats stats) stats.getQueuedSplits(), stats.getRunningSplits(), stats.getCompletedSplits(), + stats.getPlanningTimeMillis(), + stats.getAnalysisTimeMillis(), stats.getCpuTimeMillis(), stats.getWallTimeMillis(), stats.getQueuedTimeMillis(), stats.getElapsedTimeMillis(), + stats.getFinishingTimeMillis(), + stats.getPhysicalInputTimeMillis(), stats.getProcessedRows(), stats.getProcessedBytes(), stats.getPeakMemoryBytes(), + stats.getPhysicalInputBytes(), + stats.getPhysicalWrittenBytes(), + stats.getInternalNetworkInputBytes(), Optional.ofNullable(stats.getRootStage()).map(StageStats::create)); } @@ -154,6 +182,16 @@ public int getCompletedSplits() return completedSplits; } + public long getPlanningTimeMillis() + { + return planningTimeMillis; + } + + public long getAnalysisTimeMillis() + { + return analysisTimeMillis; + } + public long getCpuTimeMillis() { return cpuTimeMillis; @@ -174,6 +212,16 @@ public long getElapsedTimeMillis() return elapsedTimeMillis; } + public long getFinishingTimeMillis() + { + return finishingTimeMillis; + } + + public long getPhysicalInputTimeMillis() + { + return physicalInputTimeMillis; + } + public long getProcessedRows() { return processedRows; @@ -189,6 +237,21 @@ public long getPeakMemoryBytes() return peakMemoryBytes; } + public long getPhysicalInputBytes() + { + return physicalInputBytes; + } + + public long getPhysicalWrittenBytes() + { + return physicalWrittenBytes; + } + + public long getInternalNetworkInputBytes() + { + return internalNetworkInputBytes; + } + public Optional getRootStage() { return rootStage; diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestAsyncResultIterator.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestAsyncResultIterator.java index dd41950adfd6..963c782f934a 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestAsyncResultIterator.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestAsyncResultIterator.java @@ -333,6 +333,11 @@ public StatementStats getStats() 100, 100, 100, + 100, + 100, + 100, + 100, + 100, StageStats.builder() .setStageId("id") .setDone(false) 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 000b47195e8c..76d7ff7e5955 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 @@ -99,7 +99,7 @@ private String newQueryResults(Integer partialCancelId, Integer nextUriId, List< nextUriId == null ? null : server.url(format("/v1/statement/%s/%s", queryId, nextUriId)).uri(), responseColumns, TypedQueryData.of(data), - new StatementStats(state, state.equals("QUEUED"), true, OptionalDouble.of(0), OptionalDouble.of(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null), + new StatementStats(state, state.equals("QUEUED"), true, OptionalDouble.of(0), OptionalDouble.of(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null), null, ImmutableList.of(), null, diff --git a/core/trino-grammar/pom.xml b/core/trino-grammar/pom.xml index 5e4790b63bb0..42b3bed260eb 100644 --- a/core/trino-grammar/pom.xml +++ b/core/trino-grammar/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index 116f9749924f..2b0fb44cbfd1 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -337,7 +337,7 @@ org.apache.lucene lucene-analysis-common - 10.0.0 + 10.1.0 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 898f4b60c49b..5da2fb9fd07e 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 @@ -13,7 +13,6 @@ */ package io.trino.connector; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import io.airlift.log.Logger; @@ -37,7 +36,6 @@ import io.trino.spi.connector.SchemaRoutineName; import io.trino.spi.connector.SystemTable; import io.trino.spi.connector.TableProcedureMetadata; -import io.trino.spi.eventlistener.EventListener; import io.trino.spi.function.FunctionKind; import io.trino.spi.function.FunctionProvider; import io.trino.spi.function.table.ArgumentSpecification; @@ -83,7 +81,6 @@ public class ConnectorServices private final Optional indexProvider; private final Optional partitioningProvider; private final Optional accessControl; - private final List eventListeners; private final Map> sessionProperties; private final Map> tableProperties; private final Map> viewProperties; @@ -184,10 +181,6 @@ public ConnectorServices(Tracer tracer, CatalogHandle catalogHandle, Connector c verifyAccessControl(accessControl); this.accessControl = Optional.ofNullable(accessControl); - Iterable eventListeners = connector.getEventListeners(); - requireNonNull(eventListeners, format("Connector '%s' returned a null event listeners iterable", eventListeners)); - this.eventListeners = ImmutableList.copyOf(eventListeners); - List> sessionProperties = connector.getSessionProperties(); requireNonNull(sessionProperties, format("Connector '%s' returned a null system properties set", catalogHandle)); this.sessionProperties = Maps.uniqueIndex(sessionProperties, PropertyMetadata::getName); @@ -297,11 +290,6 @@ public Optional getAccessControl() return accessControl; } - public List getEventListeners() - { - return eventListeners; - } - public Map> getSessionProperties() { return sessionProperties; 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 3c6e16f10a6e..a28449bfe5ad 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 @@ -22,7 +22,6 @@ import io.trino.security.AccessControl; import io.trino.spi.Page; import io.trino.spi.PageBuilder; -import io.trino.spi.block.Block; import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.connector.ConnectorPageSource; @@ -141,18 +140,12 @@ public InformationSchemaPageSource( .boxed() .collect(toImmutableMap(i -> columnMetadata.get(i).getName(), Function.identity())); - List channels = columns.stream() + int[] channels = columns.stream() .map(columnHandle -> (InformationSchemaColumnHandle) columnHandle) - .map(columnHandle -> columnNameToChannel.get(columnHandle.columnName())) - .collect(toImmutableList()); + .mapToInt(columnHandle -> columnNameToChannel.get(columnHandle.columnName())) + .toArray(); - projection = page -> { - Block[] blocks = new Block[channels.size()]; - for (int i = 0; i < blocks.length; i++) { - blocks[i] = page.getBlock(channels.get(i)); - } - return new Page(page.getPositionCount(), blocks); - }; + projection = page -> page.getColumns(channels); } @Override diff --git a/core/trino-main/src/main/java/io/trino/execution/QueryStateMachine.java b/core/trino-main/src/main/java/io/trino/execution/QueryStateMachine.java index a46a02b28f2c..4b94bbdd1fa7 100644 --- a/core/trino-main/src/main/java/io/trino/execution/QueryStateMachine.java +++ b/core/trino-main/src/main/java/io/trino/execution/QueryStateMachine.java @@ -547,6 +547,7 @@ private BasicQueryStats createBasicQueryStats(BasicStageStats stageStats) stageStats.getSpilledDataSize(), stageStats.getPhysicalInputDataSize(), stageStats.getPhysicalWrittenDataSize(), + stageStats.getInternalNetworkInputDataSize(), stageStats.getCumulativeUserMemory(), stageStats.getFailedCumulativeUserMemory(), @@ -555,10 +556,14 @@ private BasicQueryStats createBasicQueryStats(BasicStageStats stageStats) succinctBytes(getPeakUserMemoryInBytes()), succinctBytes(getPeakTotalMemoryInBytes()), + queryStateTimer.getPlanningTime(), + queryStateTimer.getAnalysisTime(), stageStats.getTotalCpuTime(), stageStats.getFailedCpuTime(), stageStats.getTotalScheduledTime(), stageStats.getFailedScheduledTime(), + queryStateTimer.getFinishingTime(), + stageStats.getPhysicalInputReadTime(), stageStats.isFullyBlocked(), stageStats.getBlockedReasons(), diff --git a/core/trino-main/src/main/java/io/trino/operator/ExchangeOperator.java b/core/trino-main/src/main/java/io/trino/operator/ExchangeOperator.java index 2c1c42b8e0df..ad44dc66b8f5 100644 --- a/core/trino-main/src/main/java/io/trino/operator/ExchangeOperator.java +++ b/core/trino-main/src/main/java/io/trino/operator/ExchangeOperator.java @@ -70,9 +70,9 @@ public ExchangeOperatorFactory( ExchangeManagerRegistry exchangeManagerRegistry) { this.operatorId = operatorId; - this.sourceId = sourceId; - this.directExchangeClientSupplier = directExchangeClientSupplier; - this.serdeFactory = serdeFactory; + this.sourceId = requireNonNull(sourceId, "sourceId is null"); + this.directExchangeClientSupplier = requireNonNull(directExchangeClientSupplier, "directExchangeClientSupplier is null"); + this.serdeFactory = requireNonNull(serdeFactory, "serdeFactory is null"); this.retryPolicy = requireNonNull(retryPolicy, "retryPolicy is null"); this.exchangeManagerRegistry = requireNonNull(exchangeManagerRegistry, "exchangeManagerRegistry is null"); } diff --git a/core/trino-main/src/main/java/io/trino/operator/SplitOperatorInfo.java b/core/trino-main/src/main/java/io/trino/operator/SplitOperatorInfo.java index 7a3cac6f6a8d..af8902db194e 100644 --- a/core/trino-main/src/main/java/io/trino/operator/SplitOperatorInfo.java +++ b/core/trino-main/src/main/java/io/trino/operator/SplitOperatorInfo.java @@ -15,14 +15,17 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import io.trino.spi.Mergeable; import io.trino.spi.connector.CatalogHandle; +import java.util.List; import java.util.Map; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.util.Objects.requireNonNull; public class SplitOperatorInfo - implements OperatorInfo + implements OperatorInfo, Mergeable { private final CatalogHandle catalogHandle; private final Map splitInfo; @@ -53,4 +56,18 @@ public CatalogHandle getCatalogHandle() { return catalogHandle; } + + @Override + public SplitOperatorInfo mergeWith(SplitOperatorInfo other) + { + return mergeWith(List.of(other)); + } + + @Override + public SplitOperatorInfo mergeWith(List others) + { + return new SplitOperatorInfo( + catalogHandle, + splitInfo.entrySet().stream().collect(toImmutableMap(Map.Entry::getKey, e -> e.getValue() + " (" + others.size() + " more)"))); + } } diff --git a/core/trino-main/src/main/java/io/trino/server/BasicQueryStats.java b/core/trino-main/src/main/java/io/trino/server/BasicQueryStats.java index 76d7b10400ae..b4b485ed5126 100644 --- a/core/trino-main/src/main/java/io/trino/server/BasicQueryStats.java +++ b/core/trino-main/src/main/java/io/trino/server/BasicQueryStats.java @@ -57,16 +57,21 @@ public class BasicQueryStats private final DataSize spilledDataSize; private final DataSize physicalInputDataSize; private final DataSize physicalWrittenDataSize; + private final DataSize internalNetworkInputDataSize; private final double cumulativeUserMemory; private final double failedCumulativeUserMemory; private final DataSize userMemoryReservation; private final DataSize totalMemoryReservation; private final DataSize peakUserMemoryReservation; private final DataSize peakTotalMemoryReservation; + private final Duration planningTime; + private final Duration analysisTime; private final Duration totalCpuTime; private final Duration failedCpuTime; private final Duration totalScheduledTime; private final Duration failedScheduledTime; + private final Duration finishingTime; + private final Duration physicalInputReadTime; private final boolean fullyBlocked; private final Set blockedReasons; @@ -92,16 +97,21 @@ public BasicQueryStats( @JsonProperty("spilledDataSize") DataSize spilledDataSize, @JsonProperty("physicalInputDataSize") DataSize physicalInputDataSize, @JsonProperty("physicalWrittenDataSize") DataSize physicalWrittenDataSize, + @JsonProperty("internalNetworkInputDataSize") DataSize internalNetworkInputDataSize, @JsonProperty("cumulativeUserMemory") double cumulativeUserMemory, @JsonProperty("failedCumulativeUserMemory") double failedCumulativeUserMemory, @JsonProperty("userMemoryReservation") DataSize userMemoryReservation, @JsonProperty("totalMemoryReservation") DataSize totalMemoryReservation, @JsonProperty("peakUserMemoryReservation") DataSize peakUserMemoryReservation, @JsonProperty("peakTotalMemoryReservation") DataSize peakTotalMemoryReservation, + @JsonProperty("planningTime") Duration planningTime, + @JsonProperty("analysisTime") Duration analysisTime, @JsonProperty("totalCpuTime") Duration totalCpuTime, @JsonProperty("failedCpuTime") Duration failedCpuTime, @JsonProperty("totalScheduledTime") Duration totalScheduledTime, @JsonProperty("failedScheduledTime") Duration failedScheduledTime, + @JsonProperty("finishingTime") Duration finishingTime, + @JsonProperty("physicalInputReadTime") Duration physicalInputReadTime, @JsonProperty("fullyBlocked") boolean fullyBlocked, @JsonProperty("blockedReasons") Set blockedReasons, @JsonProperty("progressPercentage") OptionalDouble progressPercentage, @@ -133,6 +143,7 @@ public BasicQueryStats( this.spilledDataSize = spilledDataSize; this.physicalInputDataSize = physicalInputDataSize; this.physicalWrittenDataSize = physicalWrittenDataSize; + this.internalNetworkInputDataSize = internalNetworkInputDataSize; this.cumulativeUserMemory = cumulativeUserMemory; this.failedCumulativeUserMemory = failedCumulativeUserMemory; @@ -140,10 +151,14 @@ public BasicQueryStats( this.totalMemoryReservation = totalMemoryReservation; this.peakUserMemoryReservation = peakUserMemoryReservation; this.peakTotalMemoryReservation = peakTotalMemoryReservation; + this.planningTime = planningTime; + this.analysisTime = analysisTime; this.totalCpuTime = totalCpuTime; this.failedCpuTime = failedCpuTime; this.totalScheduledTime = totalScheduledTime; this.failedScheduledTime = failedScheduledTime; + this.finishingTime = finishingTime; + this.physicalInputReadTime = physicalInputReadTime; this.fullyBlocked = fullyBlocked; this.blockedReasons = ImmutableSet.copyOf(requireNonNull(blockedReasons, "blockedReasons is null")); @@ -170,16 +185,21 @@ public BasicQueryStats(QueryStats queryStats) queryStats.getSpilledDataSize(), queryStats.getPhysicalInputDataSize(), queryStats.getPhysicalWrittenDataSize(), + queryStats.getInternalNetworkInputDataSize(), queryStats.getCumulativeUserMemory(), queryStats.getFailedCumulativeUserMemory(), queryStats.getUserMemoryReservation(), queryStats.getTotalMemoryReservation(), queryStats.getPeakUserMemoryReservation(), queryStats.getPeakTotalMemoryReservation(), + queryStats.getPlanningTime(), + queryStats.getAnalysisTime(), queryStats.getTotalCpuTime(), queryStats.getFailedCpuTime(), queryStats.getTotalScheduledTime(), queryStats.getFailedScheduledTime(), + queryStats.getFinishingTime(), + queryStats.getPhysicalInputReadTime(), queryStats.isFullyBlocked(), queryStats.getBlockedReasons(), queryStats.getProgressPercentage(), @@ -206,6 +226,7 @@ public static BasicQueryStats immediateFailureQueryStats() DataSize.ofBytes(0), DataSize.ofBytes(0), DataSize.ofBytes(0), + DataSize.ofBytes(0), 0, 0, DataSize.ofBytes(0), @@ -216,6 +237,10 @@ public static BasicQueryStats immediateFailureQueryStats() new Duration(0, MILLISECONDS), new Duration(0, MILLISECONDS), new Duration(0, MILLISECONDS), + new Duration(0, MILLISECONDS), + new Duration(0, MILLISECONDS), + new Duration(0, MILLISECONDS), + new Duration(0, MILLISECONDS), false, ImmutableSet.of(), OptionalDouble.empty(), @@ -318,6 +343,12 @@ public DataSize getPhysicalWrittenDataSize() return physicalWrittenDataSize; } + @JsonProperty + public DataSize getInternalNetworkInputDataSize() + { + return internalNetworkInputDataSize; + } + @JsonProperty public double getCumulativeUserMemory() { @@ -354,6 +385,18 @@ public DataSize getPeakTotalMemoryReservation() return peakTotalMemoryReservation; } + @JsonProperty + public Duration getPlanningTime() + { + return planningTime; + } + + @JsonProperty + public Duration getAnalysisTime() + { + return analysisTime; + } + @JsonProperty public Duration getTotalCpuTime() { @@ -378,6 +421,18 @@ public Duration getFailedScheduledTime() return failedScheduledTime; } + @JsonProperty + public Duration getFinishingTime() + { + return finishingTime; + } + + @JsonProperty + public Duration getPhysicalInputReadTime() + { + return physicalInputReadTime; + } + @JsonProperty public boolean isFullyBlocked() { diff --git a/core/trino-main/src/main/java/io/trino/server/NodeResource.java b/core/trino-main/src/main/java/io/trino/server/NodeResource.java index 06244e74b208..b92b127573b6 100644 --- a/core/trino-main/src/main/java/io/trino/server/NodeResource.java +++ b/core/trino-main/src/main/java/io/trino/server/NodeResource.java @@ -26,6 +26,7 @@ import static io.trino.server.security.ResourceSecurity.AccessType.MANAGEMENT_READ; @Path("/v1/node") +@ResourceSecurity(MANAGEMENT_READ) public class NodeResource { private final HeartbeatFailureDetector failureDetector; @@ -36,14 +37,12 @@ public NodeResource(HeartbeatFailureDetector failureDetector) this.failureDetector = failureDetector; } - @ResourceSecurity(MANAGEMENT_READ) @GET public Collection getNodeStats() { return failureDetector.getStats().values(); } - @ResourceSecurity(MANAGEMENT_READ) @GET @Path("failed") public Collection getFailed() diff --git a/core/trino-main/src/main/java/io/trino/server/QueryResource.java b/core/trino-main/src/main/java/io/trino/server/QueryResource.java index 1aef011787d4..fa9e4f9c4987 100644 --- a/core/trino-main/src/main/java/io/trino/server/QueryResource.java +++ b/core/trino-main/src/main/java/io/trino/server/QueryResource.java @@ -55,6 +55,7 @@ * Manage queries scheduled on this node */ @Path("/v1/query") +@ResourceSecurity(AUTHENTICATED_USER) public class QueryResource { private final DispatchManager dispatchManager; @@ -69,7 +70,6 @@ public QueryResource(DispatchManager dispatchManager, AccessControl accessContro this.sessionContextFactory = requireNonNull(sessionContextFactory, "sessionContextFactory is null"); } - @ResourceSecurity(AUTHENTICATED_USER) @GET public List getAllQueryInfo(@QueryParam("state") String stateFilter, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) { @@ -87,7 +87,6 @@ public List getAllQueryInfo(@QueryParam("state") String stateFil return builder.build(); } - @ResourceSecurity(AUTHENTICATED_USER) @GET @Path("{queryId}") public Response getQueryInfo(@PathParam("queryId") QueryId queryId, @QueryParam("pruned") @DefaultValue("false") boolean pruned, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) @@ -108,7 +107,6 @@ public Response getQueryInfo(@PathParam("queryId") QueryId queryId, @QueryParam( } } - @ResourceSecurity(AUTHENTICATED_USER) @DELETE @Path("{queryId}") public void cancelQuery(@PathParam("queryId") QueryId queryId, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) @@ -127,7 +125,6 @@ public void cancelQuery(@PathParam("queryId") QueryId queryId, @Context HttpServ } } - @ResourceSecurity(AUTHENTICATED_USER) @PUT @Path("{queryId}/killed") public Response killQuery(@PathParam("queryId") QueryId queryId, String message, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) @@ -135,7 +132,6 @@ public Response killQuery(@PathParam("queryId") QueryId queryId, String message, return failQuery(queryId, createKillQueryException(message), servletRequest, httpHeaders); } - @ResourceSecurity(AUTHENTICATED_USER) @PUT @Path("{queryId}/preempted") public Response preemptQuery(@PathParam("queryId") QueryId queryId, String message, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) diff --git a/core/trino-main/src/main/java/io/trino/server/QueryStateInfoResource.java b/core/trino-main/src/main/java/io/trino/server/QueryStateInfoResource.java index bbf16bcede64..dfc00faba788 100644 --- a/core/trino-main/src/main/java/io/trino/server/QueryStateInfoResource.java +++ b/core/trino-main/src/main/java/io/trino/server/QueryStateInfoResource.java @@ -49,6 +49,7 @@ import static java.util.Objects.requireNonNull; @Path("/v1/queryState") +@ResourceSecurity(AUTHENTICATED_USER) public class QueryStateInfoResource { private final DispatchManager dispatchManager; @@ -69,7 +70,6 @@ public QueryStateInfoResource( this.sessionContextFactory = requireNonNull(sessionContextFactory, "sessionContextFactory is null"); } - @ResourceSecurity(AUTHENTICATED_USER) @GET @Produces(MediaType.APPLICATION_JSON) public List getQueryStateInfos(@QueryParam("user") String user, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) @@ -102,7 +102,6 @@ private QueryStateInfo getQueryStateInfo(BasicQueryInfo queryInfo) return createQueryStateInfo(queryInfo, groupId); } - @ResourceSecurity(AUTHENTICATED_USER) @GET @Path("{queryId}") @Produces(MediaType.APPLICATION_JSON) diff --git a/core/trino-main/src/main/java/io/trino/server/ResourceGroupStateInfoResource.java b/core/trino-main/src/main/java/io/trino/server/ResourceGroupStateInfoResource.java index b3f743283467..33f2c722b72b 100644 --- a/core/trino-main/src/main/java/io/trino/server/ResourceGroupStateInfoResource.java +++ b/core/trino-main/src/main/java/io/trino/server/ResourceGroupStateInfoResource.java @@ -35,6 +35,7 @@ import static java.util.Objects.requireNonNull; @Path("/v1/resourceGroupState") +@ResourceSecurity(MANAGEMENT_READ) public class ResourceGroupStateInfoResource { private final ResourceGroupInfoProvider resourceGroupInfoProvider; @@ -45,7 +46,6 @@ public ResourceGroupStateInfoResource(ResourceGroupInfoProvider resourceGroupInf this.resourceGroupInfoProvider = requireNonNull(resourceGroupInfoProvider, "resourceGroupInfoProvider is null"); } - @ResourceSecurity(MANAGEMENT_READ) @GET @Produces(MediaType.APPLICATION_JSON) @Encoded diff --git a/core/trino-main/src/main/java/io/trino/server/Server.java b/core/trino-main/src/main/java/io/trino/server/Server.java index fd7bf81b84bb..a0a56b78bc06 100644 --- a/core/trino-main/src/main/java/io/trino/server/Server.java +++ b/core/trino-main/src/main/java/io/trino/server/Server.java @@ -13,7 +13,6 @@ */ package io.trino.server; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.StandardSystemProperty; import com.google.common.collect.ImmutableList; import com.google.inject.Injector; @@ -44,7 +43,6 @@ import io.trino.connector.CatalogManagerConfig.CatalogMangerKind; import io.trino.connector.CatalogManagerModule; import io.trino.connector.CatalogStoreManager; -import io.trino.connector.ConnectorServices; import io.trino.connector.ConnectorServicesProvider; import io.trino.eventlistener.EventListenerManager; import io.trino.eventlistener.EventListenerModule; @@ -72,7 +70,6 @@ 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.Locale; import java.util.Optional; @@ -84,7 +81,6 @@ import static io.trino.server.TrinoSystemRequirements.verifySystemRequirements; import static java.lang.String.format; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; -import static java.util.function.Predicate.not; import static java.util.stream.Collectors.joining; public class Server @@ -157,14 +153,8 @@ private void doStart(String trinoVersion) // Only static catalog manager announces catalogs // Connector event listeners are only supported for statically loaded catalogs - // TODO: remove connector event listeners or add support for dynamic loading from connector if (injector.getInstance(CatalogManagerConfig.class).getCatalogMangerKind() == CatalogMangerKind.STATIC) { CatalogManager catalogManager = injector.getInstance(CatalogManager.class); - addConnectorEventListeners( - catalogManager, - injector.getInstance(ConnectorServicesProvider.class), - injector.getInstance(EventListenerManager.class)); - // TODO: remove this huge hack updateConnectorIds(injector.getInstance(Announcer.class), catalogManager); } @@ -211,23 +201,6 @@ private void doStart(String trinoVersion) } } - @VisibleForTesting - public static void addConnectorEventListeners( - CatalogManager catalogManager, - ConnectorServicesProvider connectorServicesProvider, - EventListenerManager eventListenerManager) - { - catalogManager.getCatalogNames().stream() - .map(catalogManager::getCatalog) - .flatMap(Optional::stream) - .filter(not(Catalog::isFailed)) - .map(Catalog::getCatalogHandle) - .map(connectorServicesProvider::getConnectorServices) - .map(ConnectorServices::getEventListeners) - .flatMap(Collection::stream) - .forEach(eventListenerManager::addEventListener); - } - private static void addMessages(StringBuilder output, String type, List messages) { if (messages.isEmpty()) { diff --git a/core/trino-main/src/main/java/io/trino/server/StatusResource.java b/core/trino-main/src/main/java/io/trino/server/StatusResource.java index 25a640f8b4ae..f9f227887fb4 100644 --- a/core/trino-main/src/main/java/io/trino/server/StatusResource.java +++ b/core/trino-main/src/main/java/io/trino/server/StatusResource.java @@ -34,6 +34,7 @@ import static java.util.Objects.requireNonNull; @Path("/v1/status") +@ResourceSecurity(PUBLIC) public class StatusResource { private final NodeInfo nodeInfo; @@ -64,7 +65,6 @@ public StatusResource(NodeVersion nodeVersion, NodeInfo nodeInfo, ServerConfig s } } - @ResourceSecurity(PUBLIC) @HEAD @Produces(APPLICATION_JSON) // to match the GET route public Response statusPing() @@ -72,7 +72,6 @@ public Response statusPing() return Response.ok().build(); } - @ResourceSecurity(PUBLIC) @GET @Produces(APPLICATION_JSON) public NodeStatus getStatus() diff --git a/core/trino-main/src/main/java/io/trino/server/TaskExecutorResource.java b/core/trino-main/src/main/java/io/trino/server/TaskExecutorResource.java index 0f8d19d52fba..e8d0abc6a06e 100644 --- a/core/trino-main/src/main/java/io/trino/server/TaskExecutorResource.java +++ b/core/trino-main/src/main/java/io/trino/server/TaskExecutorResource.java @@ -25,6 +25,7 @@ import static java.util.Objects.requireNonNull; @Path("/v1/maxActiveSplits") +@ResourceSecurity(MANAGEMENT_READ) public class TaskExecutorResource { private final TimeSharingTaskExecutor taskExecutor; @@ -36,7 +37,6 @@ public TaskExecutorResource( this.taskExecutor = requireNonNull(taskExecutor, "taskExecutor is null"); } - @ResourceSecurity(MANAGEMENT_READ) @GET @Produces(MediaType.TEXT_PLAIN) public String getMaxActiveSplit() diff --git a/core/trino-main/src/main/java/io/trino/server/TaskResource.java b/core/trino-main/src/main/java/io/trino/server/TaskResource.java index ed2057e93b1a..97ac4d980aa8 100644 --- a/core/trino-main/src/main/java/io/trino/server/TaskResource.java +++ b/core/trino-main/src/main/java/io/trino/server/TaskResource.java @@ -91,6 +91,7 @@ * Manages tasks on this worker node */ @Path("/v1/task") +@ResourceSecurity(INTERNAL_ONLY) public class TaskResource { private static final Logger log = Logger.get(TaskResource.class); @@ -127,7 +128,6 @@ public TaskResource( this.failureInjector = requireNonNull(failureInjector, "failureInjector is null"); } - @ResourceSecurity(INTERNAL_ONLY) @GET @Produces(MediaType.APPLICATION_JSON) public List getAllTaskInfo(@Context UriInfo uriInfo) @@ -139,7 +139,6 @@ public List getAllTaskInfo(@Context UriInfo uriInfo) return allTaskInfo; } - @ResourceSecurity(INTERNAL_ONLY) @POST @Path("{taskId}") @Consumes(MediaType.APPLICATION_JSON) @@ -178,7 +177,6 @@ public void createOrUpdateTask( asyncResponse.resume(Response.ok().entity(taskInfo).build()); } - @ResourceSecurity(INTERNAL_ONLY) @GET @Path("{taskId}") @Produces(MediaType.APPLICATION_JSON) @@ -228,7 +226,6 @@ public void getTaskInfo( bindAsyncResponse(asyncResponse, withFallbackAfterTimeout(response, timeout, () -> serviceUnavailable(timeout), timeoutExecutor), responseExecutor); } - @ResourceSecurity(INTERNAL_ONLY) @GET @Path("{taskId}/status") @Produces(MediaType.APPLICATION_JSON) @@ -273,7 +270,6 @@ public void getTaskStatus( bindAsyncResponse(asyncResponse, withFallbackAfterTimeout(response, timeout, () -> serviceUnavailable(timeout), timeoutExecutor), responseExecutor); } - @ResourceSecurity(INTERNAL_ONLY) @GET @Path("{taskId}/dynamicfilters") @Produces(MediaType.APPLICATION_JSON) @@ -295,7 +291,6 @@ public void acknowledgeAndGetNewDynamicFilterDomains( asyncResponse.resume(taskManager.acknowledgeAndGetNewDynamicFilterDomains(taskId, currentDynamicFiltersVersion)); } - @ResourceSecurity(INTERNAL_ONLY) @DELETE @Path("{taskId}") @Produces(MediaType.APPLICATION_JSON) @@ -320,7 +315,6 @@ public TaskInfo deleteTask( return taskInfo; } - @ResourceSecurity(INTERNAL_ONLY) @POST @Path("{taskId}/fail") @Consumes(MediaType.APPLICATION_JSON) @@ -334,7 +328,6 @@ public TaskInfo failTask( return taskManager.failTask(taskId, failTaskRequest.getFailureInfo().toException()); } - @ResourceSecurity(INTERNAL_ONLY) @GET @Path("{taskId}/results/{bufferId}/{token}") @Produces(TRINO_PAGES) @@ -375,7 +368,6 @@ public void getResults( responseFuture.addListener(() -> readFromOutputBufferTime.add(Duration.nanosSince(start)), directExecutor()); } - @ResourceSecurity(INTERNAL_ONLY) @GET @Path("{taskId}/results/{bufferId}/{token}/acknowledge") public Response acknowledgeResults( @@ -390,7 +382,6 @@ public Response acknowledgeResults( return Response.ok().build(); } - @ResourceSecurity(INTERNAL_ONLY) @DELETE @Path("{taskId}/results/{bufferId}") public void destroyTaskResults( @@ -409,7 +400,6 @@ public void destroyTaskResults( asyncResponse.resume(Response.noContent().build()); } - @ResourceSecurity(INTERNAL_ONLY) @POST @Path("pruneCatalogs") @Consumes(MediaType.APPLICATION_JSON) diff --git a/core/trino-main/src/main/java/io/trino/server/ThreadResource.java b/core/trino-main/src/main/java/io/trino/server/ThreadResource.java index 9a540e9f2bef..47a5ad2b74b4 100644 --- a/core/trino-main/src/main/java/io/trino/server/ThreadResource.java +++ b/core/trino-main/src/main/java/io/trino/server/ThreadResource.java @@ -37,9 +37,9 @@ import static java.util.Comparator.comparing; @Path("/v1/thread") +@ResourceSecurity(MANAGEMENT_READ) public class ThreadResource { - @ResourceSecurity(MANAGEMENT_READ) @GET @Produces(MediaType.APPLICATION_JSON) public List getThreadInfo() diff --git a/core/trino-main/src/main/java/io/trino/server/protocol/ExecutingStatementResource.java b/core/trino-main/src/main/java/io/trino/server/protocol/ExecutingStatementResource.java index eef5b7185c8e..41bb19f64fa1 100644 --- a/core/trino-main/src/main/java/io/trino/server/protocol/ExecutingStatementResource.java +++ b/core/trino-main/src/main/java/io/trino/server/protocol/ExecutingStatementResource.java @@ -72,6 +72,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; @Path("/v1/statement/executing") +@ResourceSecurity(PUBLIC) public class ExecutingStatementResource { private static final Logger log = Logger.get(ExecutingStatementResource.class); @@ -156,7 +157,6 @@ public void stop() queryPurger.shutdownNow(); } - @ResourceSecurity(PUBLIC) @GET @Path("{queryId}/{slug}/{token}") @Produces(MediaType.APPLICATION_JSON) @@ -295,7 +295,6 @@ private Response toResponse(QueryResultsResponse resultsResponse, Optional generator.writeFieldName(Base64.getEncoder().encodeToString(varbinary.getBytes())); - case Object value -> generator.writeFieldName(value.toString()); - } + generator.writeFieldName(mapType.getKeyType().getObjectValue(session, keyBlock, offset + i).toString()); valueEncoder.encode(generator, session, valueBlock, offset + i); } generator.writeEndObject(); diff --git a/core/trino-main/src/main/java/io/trino/server/protocol/ProtocolUtil.java b/core/trino-main/src/main/java/io/trino/server/protocol/ProtocolUtil.java index 61adcb946f45..7894c6fb68b8 100644 --- a/core/trino-main/src/main/java/io/trino/server/protocol/ProtocolUtil.java +++ b/core/trino-main/src/main/java/io/trino/server/protocol/ProtocolUtil.java @@ -182,14 +182,19 @@ public static StatementStats toStatementStats(ResultQueryInfo queryInfo) .setQueuedSplits(queryStats.getQueuedDrivers()) .setRunningSplits(queryStats.getRunningDrivers() + queryStats.getBlockedDrivers()) .setCompletedSplits(queryStats.getCompletedDrivers()) + .setPlanningTimeMillis(queryStats.getPlanningTime().toMillis()) + .setAnalysisTimeMillis(queryStats.getAnalysisTime().toMillis()) .setCpuTimeMillis(queryStats.getTotalCpuTime().toMillis()) .setWallTimeMillis(queryStats.getTotalScheduledTime().toMillis()) .setQueuedTimeMillis(queryStats.getQueuedTime().toMillis()) .setElapsedTimeMillis(queryStats.getElapsedTime().toMillis()) + .setFinishingTimeMillis(queryStats.getFinishingTime().toMillis()) + .setPhysicalInputTimeMillis(queryStats.getPhysicalInputReadTime().toMillis()) .setProcessedRows(queryStats.getRawInputPositions()) .setProcessedBytes(queryStats.getRawInputDataSize().toBytes()) .setPhysicalInputBytes(queryStats.getPhysicalInputDataSize().toBytes()) .setPhysicalWrittenBytes(queryStats.getPhysicalWrittenDataSize().toBytes()) + .setInternalNetworkInputBytes(queryStats.getInternalNetworkInputDataSize().toBytes()) .setPeakMemoryBytes(queryStats.getPeakUserMemoryReservation().toBytes()) .setSpilledBytes(queryStats.getSpilledDataSize().toBytes()) .setRootStage(rootStageStats) diff --git a/core/trino-main/src/main/java/io/trino/server/protocol/spooling/CoordinatorSegmentResource.java b/core/trino-main/src/main/java/io/trino/server/protocol/spooling/CoordinatorSegmentResource.java index e08c1a31a251..dbbfd855622e 100644 --- a/core/trino-main/src/main/java/io/trino/server/protocol/spooling/CoordinatorSegmentResource.java +++ b/core/trino-main/src/main/java/io/trino/server/protocol/spooling/CoordinatorSegmentResource.java @@ -64,7 +64,6 @@ public CoordinatorSegmentResource(SpoolingManager spoolingManager, SpoolingConfi @GET @Path("/download/{identifier}") @Produces(MediaType.APPLICATION_OCTET_STREAM) - @ResourceSecurity(PUBLIC) public Response download(@Context UriInfo uriInfo, @PathParam("identifier") String identifier, @Context HttpHeaders headers) throws IOException { @@ -92,7 +91,6 @@ public Response download(@Context UriInfo uriInfo, @PathParam("identifier") Stri @GET @Path("/ack/{identifier}") - @ResourceSecurity(PUBLIC) public Response acknowledge(@PathParam("identifier") String identifier, @Context HttpHeaders headers) throws IOException { diff --git a/core/trino-main/src/main/java/io/trino/server/protocol/spooling/WorkerSegmentResource.java b/core/trino-main/src/main/java/io/trino/server/protocol/spooling/WorkerSegmentResource.java index 69cc3b239568..703054cd7df3 100644 --- a/core/trino-main/src/main/java/io/trino/server/protocol/spooling/WorkerSegmentResource.java +++ b/core/trino-main/src/main/java/io/trino/server/protocol/spooling/WorkerSegmentResource.java @@ -47,7 +47,6 @@ public WorkerSegmentResource(SpoolingManager spoolingManager) @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) - @ResourceSecurity(PUBLIC) public Response download(@PathParam("identifier") String identifier, @Context HttpHeaders headers) throws IOException { diff --git a/core/trino-main/src/main/java/io/trino/server/security/ResourceAccessType.java b/core/trino-main/src/main/java/io/trino/server/security/ResourceAccessType.java index 9fe79418b358..4dba39d87d5d 100644 --- a/core/trino-main/src/main/java/io/trino/server/security/ResourceAccessType.java +++ b/core/trino-main/src/main/java/io/trino/server/security/ResourceAccessType.java @@ -46,20 +46,14 @@ public AccessType getAccessType(ResourceInfo resourceInfo) if (accessType.isPresent()) { return accessType.get(); } - // check if the resource class has an access type declared for all methods - accessType = resourceAccessTypeLoader.getAccessType(resourceInfo.getResourceClass()); - if (accessType.isPresent()) { - verifyNotTrinoResource(resourceInfo); - return accessType.get(); - } - // in some cases there the resource is a nested class, so check the parent class - // we currently only check one level, but we could handle multiple nesting levels if necessary - if (resourceInfo.getResourceClass().getDeclaringClass() != null) { - accessType = resourceAccessTypeLoader.getAccessType(resourceInfo.getResourceClass().getDeclaringClass()); + // check if the resource class or enclosing classes have an access type declared for all methods + Class current = resourceInfo.getResourceClass(); + while (current != null) { + accessType = resourceAccessTypeLoader.getAccessType(current); if (accessType.isPresent()) { - verifyNotTrinoResource(resourceInfo); return accessType.get(); } + current = current.getDeclaringClass(); } } // Trino resources are required to have a declared access control diff --git a/core/trino-main/src/main/java/io/trino/server/security/oauth2/OAuth2CallbackResource.java b/core/trino-main/src/main/java/io/trino/server/security/oauth2/OAuth2CallbackResource.java index 8539cf94d4a0..7a170c6ffe29 100644 --- a/core/trino-main/src/main/java/io/trino/server/security/oauth2/OAuth2CallbackResource.java +++ b/core/trino-main/src/main/java/io/trino/server/security/oauth2/OAuth2CallbackResource.java @@ -37,6 +37,7 @@ import static java.util.Objects.requireNonNull; @Path(CALLBACK_ENDPOINT) +@ResourceSecurity(PUBLIC) public class OAuth2CallbackResource { private static final Logger LOG = Logger.get(OAuth2CallbackResource.class); @@ -51,7 +52,6 @@ public OAuth2CallbackResource(OAuth2Service service) this.service = requireNonNull(service, "service is null"); } - @ResourceSecurity(PUBLIC) @GET @Produces(TEXT_HTML) public Response callback( diff --git a/core/trino-main/src/main/java/io/trino/server/security/oauth2/OAuth2TokenExchangeResource.java b/core/trino-main/src/main/java/io/trino/server/security/oauth2/OAuth2TokenExchangeResource.java index 6a359d1df485..cfdc470ccb8b 100644 --- a/core/trino-main/src/main/java/io/trino/server/security/oauth2/OAuth2TokenExchangeResource.java +++ b/core/trino-main/src/main/java/io/trino/server/security/oauth2/OAuth2TokenExchangeResource.java @@ -55,6 +55,7 @@ import static java.util.Objects.requireNonNull; @Path(OAuth2TokenExchangeResource.TOKEN_ENDPOINT) +@ResourceSecurity(PUBLIC) public class OAuth2TokenExchangeResource { static final String TOKEN_ENDPOINT = "/oauth2/token/"; @@ -75,7 +76,6 @@ public OAuth2TokenExchangeResource(OAuth2TokenExchange tokenExchange, OAuth2Serv this.timeoutExecutor = executor.getScheduledExecutor(); } - @ResourceSecurity(PUBLIC) @Path("initiate/{authIdHash}") @GET @Produces(MediaType.APPLICATION_JSON) @@ -84,7 +84,6 @@ public Response initiateTokenExchange(@PathParam("authIdHash") String authIdHash return service.startOAuth2Challenge(externalUriInfo.absolutePath(CALLBACK_ENDPOINT), Optional.ofNullable(authIdHash)); } - @ResourceSecurity(PUBLIC) @Path("{authId}") @GET @Produces(MediaType.APPLICATION_JSON) @@ -119,7 +118,6 @@ private static Response pendingResponse(HttpServletRequest request) return Response.ok(jsonMap("nextUri", request.getRequestURL()), APPLICATION_JSON_TYPE).build(); } - @ResourceSecurity(PUBLIC) @DELETE @Path("{authId}") public Response deleteAuthenticationToken(@PathParam("authId") UUID authId) 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 321628772c14..54fe75471c24 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 @@ -79,7 +79,6 @@ import io.trino.server.PluginInstaller; import io.trino.server.PrefixObjectNameGeneratorModule; import io.trino.server.QuerySessionSupplier; -import io.trino.server.Server; import io.trino.server.ServerMainModule; import io.trino.server.SessionContext; import io.trino.server.SessionPropertyDefaults; @@ -529,20 +528,6 @@ public void loadSpoolingManager(String name, Map properties) spoolingManagerRegistry.loadSpoolingManager(name, properties); } - /** - * Add the event listeners from connectors. Connector event listeners are - * only supported for statically loaded catalogs, and this doesn't match up - * with the model of the testing Trino server. This method should only be - * called once after all catalogs are added. - */ - public void addConnectorEventListeners() - { - Server.addConnectorEventListeners( - injector.getInstance(CatalogManager.class), - injector.getInstance(ConnectorServicesProvider.class), - injector.getInstance(EventListenerManager.class)); - } - public Path getBaseDataDir() { return baseDataDir; diff --git a/core/trino-main/src/main/java/io/trino/server/ui/ClusterResource.java b/core/trino-main/src/main/java/io/trino/server/ui/ClusterResource.java index 4056dfe42484..5ffe6345f5ee 100644 --- a/core/trino-main/src/main/java/io/trino/server/ui/ClusterResource.java +++ b/core/trino-main/src/main/java/io/trino/server/ui/ClusterResource.java @@ -30,6 +30,7 @@ import static java.util.Objects.requireNonNull; @Path("/ui/api/cluster") +@ResourceSecurity(WEB_UI) public class ClusterResource { private final NodeVersion version; @@ -43,7 +44,6 @@ public ClusterResource(NodeVersion nodeVersion, NodeInfo nodeInfo) this.environment = nodeInfo.getEnvironment(); } - @ResourceSecurity(WEB_UI) @GET @Produces(APPLICATION_JSON) public ClusterInfo getInfo() diff --git a/core/trino-main/src/main/java/io/trino/server/ui/ClusterStatsResource.java b/core/trino-main/src/main/java/io/trino/server/ui/ClusterStatsResource.java index cb2a1cfa4da6..0125486fdf0b 100644 --- a/core/trino-main/src/main/java/io/trino/server/ui/ClusterStatsResource.java +++ b/core/trino-main/src/main/java/io/trino/server/ui/ClusterStatsResource.java @@ -37,6 +37,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; @Path("/ui/api/stats") +@ResourceSecurity(WEB_UI) public class ClusterStatsResource { private final InternalNodeManager nodeManager; @@ -53,7 +54,6 @@ public ClusterStatsResource(NodeSchedulerConfig nodeSchedulerConfig, InternalNod this.clusterMemoryManager = requireNonNull(clusterMemoryManager, "clusterMemoryManager is null"); } - @ResourceSecurity(WEB_UI) @GET @Produces(MediaType.APPLICATION_JSON) public ClusterStats getClusterStats() diff --git a/core/trino-main/src/main/java/io/trino/server/ui/LoginResource.java b/core/trino-main/src/main/java/io/trino/server/ui/LoginResource.java index ece85c67e9da..e2b33a7bcf5e 100644 --- a/core/trino-main/src/main/java/io/trino/server/ui/LoginResource.java +++ b/core/trino-main/src/main/java/io/trino/server/ui/LoginResource.java @@ -46,6 +46,7 @@ import static java.util.Objects.requireNonNull; @Path("") +@ResourceSecurity(WEB_UI) public class LoginResource { private static final String REPLACEMENT_TEXT = "
false
"; @@ -61,7 +62,6 @@ public LoginResource(FormWebUiAuthenticationFilter formWebUiAuthenticationManage verify(loginHtml.contains(REPLACEMENT_TEXT), "login.html does not contain the replacement text"); } - @ResourceSecurity(WEB_UI) @GET @Path(LOGIN_FORM) public Response getFile(@Context SecurityContext securityContext) @@ -72,7 +72,6 @@ public Response getFile(@Context SecurityContext securityContext) .build(); } - @ResourceSecurity(WEB_UI) @POST @Path(UI_LOGIN) public Response login( @@ -101,7 +100,6 @@ public Response login( .build(); } - @ResourceSecurity(WEB_UI) @GET @Path(UI_LOGOUT) public Response logout(@Context HttpHeaders httpHeaders, @Context SecurityContext securityContext, @BeanParam ExternalUriInfo externalUriInfo) diff --git a/core/trino-main/src/main/java/io/trino/server/ui/UiQueryResource.java b/core/trino-main/src/main/java/io/trino/server/ui/UiQueryResource.java index f0205bb0d0d1..c600efb4dc1f 100644 --- a/core/trino-main/src/main/java/io/trino/server/ui/UiQueryResource.java +++ b/core/trino-main/src/main/java/io/trino/server/ui/UiQueryResource.java @@ -53,6 +53,7 @@ import static java.util.Objects.requireNonNull; @Path("/ui/api/query") +@ResourceSecurity(WEB_UI) @DisableHttpCache public class UiQueryResource { @@ -68,7 +69,6 @@ public UiQueryResource(DispatchManager dispatchManager, AccessControl accessCont this.sessionContextFactory = requireNonNull(sessionContextFactory, "sessionContextFactory is null"); } - @ResourceSecurity(WEB_UI) @GET public List getAllQueryInfo(@QueryParam("state") String stateFilter, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) { @@ -86,7 +86,6 @@ public List getAllQueryInfo(@QueryParam("state") String s return builder.build(); } - @ResourceSecurity(WEB_UI) @GET @Path("{queryId}") public Response getQueryInfo(@PathParam("queryId") QueryId queryId, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) @@ -106,7 +105,6 @@ public Response getQueryInfo(@PathParam("queryId") QueryId queryId, @Context Htt throw new GoneException(); } - @ResourceSecurity(WEB_UI) @PUT @Path("{queryId}/killed") public Response killQuery(@PathParam("queryId") QueryId queryId, String message, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) @@ -114,7 +112,6 @@ public Response killQuery(@PathParam("queryId") QueryId queryId, String message, return failQuery(queryId, createKillQueryException(message), servletRequest, httpHeaders); } - @ResourceSecurity(WEB_UI) @PUT @Path("{queryId}/preempted") public Response preemptQuery(@PathParam("queryId") QueryId queryId, String message, @Context HttpServletRequest servletRequest, @Context HttpHeaders httpHeaders) diff --git a/core/trino-main/src/main/java/io/trino/server/ui/WebUiPreviewStaticResource.java b/core/trino-main/src/main/java/io/trino/server/ui/WebUiPreviewStaticResource.java index a8487c3738c5..c1b5217cee51 100644 --- a/core/trino-main/src/main/java/io/trino/server/ui/WebUiPreviewStaticResource.java +++ b/core/trino-main/src/main/java/io/trino/server/ui/WebUiPreviewStaticResource.java @@ -27,16 +27,15 @@ import static io.trino.web.ui.WebUiResources.webUiResource; @Path("/ui/preview") +@ResourceSecurity(WEB_UI) public class WebUiPreviewStaticResource { - @ResourceSecurity(WEB_UI) @GET public Response getUiPreview(@BeanParam ExternalUriInfo externalUriInfo) { return Response.seeOther(externalUriInfo.absolutePath("/ui/preview/index.html")).build(); } - @ResourceSecurity(WEB_UI) @GET @Path("{path: .*}") public Response getFile(@PathParam("path") String path) diff --git a/core/trino-main/src/main/java/io/trino/server/ui/WebUiStaticResource.java b/core/trino-main/src/main/java/io/trino/server/ui/WebUiStaticResource.java index db286b6745cc..801bef0bdffb 100644 --- a/core/trino-main/src/main/java/io/trino/server/ui/WebUiStaticResource.java +++ b/core/trino-main/src/main/java/io/trino/server/ui/WebUiStaticResource.java @@ -30,6 +30,7 @@ import static io.trino.web.ui.WebUiResources.webUiResource; @Path("") +@ResourceSecurity(PUBLIC) public class WebUiStaticResource { @ResourceSecurity(PUBLIC) @@ -39,7 +40,6 @@ public Response getRoot(@BeanParam ExternalUriInfo externalUriInfo) return Response.seeOther(externalUriInfo.absolutePath("/ui/")).build(); } - @ResourceSecurity(PUBLIC) @GET @Path("/ui") public Response getUi(@BeanParam ExternalUriInfo externalUriInfo) diff --git a/core/trino-main/src/main/java/io/trino/server/ui/WorkerResource.java b/core/trino-main/src/main/java/io/trino/server/ui/WorkerResource.java index 5fb017148de1..cee38f624f43 100644 --- a/core/trino-main/src/main/java/io/trino/server/ui/WorkerResource.java +++ b/core/trino-main/src/main/java/io/trino/server/ui/WorkerResource.java @@ -62,6 +62,7 @@ import static java.util.Objects.requireNonNull; @Path("/ui/api/worker") +@ResourceSecurity(WEB_UI) public class WorkerResource { private final DispatchManager dispatchManager; @@ -85,7 +86,6 @@ public WorkerResource( this.sessionContextFactory = requireNonNull(sessionContextFactory, "sessionContextFactory is null"); } - @ResourceSecurity(WEB_UI) @GET @Path("{nodeId}/status") public Response getStatus(@PathParam("nodeId") String nodeId) @@ -93,7 +93,6 @@ public Response getStatus(@PathParam("nodeId") String nodeId) return proxyJsonResponse(nodeId, "v1/status"); } - @ResourceSecurity(WEB_UI) @GET @Path("{nodeId}/thread") public Response getThreads(@PathParam("nodeId") String nodeId) @@ -101,7 +100,6 @@ public Response getThreads(@PathParam("nodeId") String nodeId) return proxyJsonResponse(nodeId, "v1/thread"); } - @ResourceSecurity(WEB_UI) @GET @Path("{nodeId}/task/{taskId}") public Response getThreads( @@ -124,7 +122,6 @@ public Response getThreads( throw new GoneException(); } - @ResourceSecurity(WEB_UI) @GET public Response getWorkerList() { 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 2ec18558dd32..cbdf2c3203ac 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 @@ -508,7 +508,7 @@ public LocalExecutionPlanner( this.pageSourceManager = requireNonNull(pageSourceManager, "pageSourceManager is null"); this.indexManager = requireNonNull(indexManager, "indexManager is null"); this.nodePartitioningManager = requireNonNull(nodePartitioningManager, "nodePartitioningManager is null"); - this.directExchangeClientSupplier = directExchangeClientSupplier; + this.directExchangeClientSupplier = requireNonNull(directExchangeClientSupplier, "directExchangeClientSupplier is null"); this.pageSinkManager = requireNonNull(pageSinkManager, "pageSinkManager is null"); this.expressionCompiler = requireNonNull(expressionCompiler, "expressionCompiler is null"); this.pageFunctionCompiler = requireNonNull(pageFunctionCompiler, "pageFunctionCompiler is null"); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushJoinIntoTableScan.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushJoinIntoTableScan.java index e6a71664f953..37f0054ae67d 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushJoinIntoTableScan.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushJoinIntoTableScan.java @@ -46,7 +46,6 @@ import java.util.Map; import java.util.Optional; -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.SystemSessionProperties.isAllowPushdownIntoConnectors; @@ -103,7 +102,9 @@ public Result apply(JoinNode joinNode, Captures captures, Context context) TableScanNode left = captures.get(LEFT_TABLE_SCAN); TableScanNode right = captures.get(RIGHT_TABLE_SCAN); - verify(!left.isUpdateTarget() && !right.isUpdateTarget(), "Unexpected Join over for-update table scan"); + if (left.isUpdateTarget() && !right.isUpdateTarget()) { + return Result.empty(); + } Expression effectiveFilter = getEffectiveFilter(joinNode); ConnectorExpressionTranslation translation = ConnectorExpressionTranslator.translateConjuncts( diff --git a/core/trino-main/src/main/java/io/trino/testing/PlanTester.java b/core/trino-main/src/main/java/io/trino/testing/PlanTester.java index 1b1a226da4e9..28a3992337d0 100644 --- a/core/trino-main/src/main/java/io/trino/testing/PlanTester.java +++ b/core/trino-main/src/main/java/io/trino/testing/PlanTester.java @@ -745,7 +745,9 @@ private List createDrivers(Session session, @Language("SQL") String sql) indexManager, nodePartitioningManager, pageSinkManager, - null, + (_, _, _, _, _, _) -> { + throw new UnsupportedOperationException(); + }, expressionCompiler, pageFunctionCompiler, joinFilterFunctionCompiler, 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 c4e2ecc31266..acaf65954dc7 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 @@ -143,15 +143,6 @@ public ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTable } } - @Override - public Optional getTableHandleForExecute(ConnectorSession session, ConnectorTableHandle tableHandle, String procedureName, Map executeProperties, RetryMode retryMode) - { - Span span = startSpan("getTableHandleForExecute", tableHandle); - try (var _ = scopedSpan(span)) { - return delegate.getTableHandleForExecute(session, tableHandle, procedureName, executeProperties, retryMode); - } - } - @Override public Optional getTableHandleForExecute(ConnectorSession session, ConnectorAccessControl accessControl, ConnectorTableHandle tableHandle, String procedureName, Map executeProperties, RetryMode retryMode) { @@ -761,15 +752,6 @@ public Optional getUpdateLayout(ConnectorSession se } } - @Override - public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, RetryMode retryMode) - { - Span span = startSpan("beginMerge", tableHandle); - try (var _ = scopedSpan(span)) { - return delegate.beginMerge(session, tableHandle, retryMode); - } - } - @Override public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, Map> updateCaseColumns, RetryMode retryMode) { diff --git a/core/trino-main/src/main/java/io/trino/util/CompilerUtils.java b/core/trino-main/src/main/java/io/trino/util/CompilerUtils.java index 4b371a5dd8b9..931f50750dd0 100644 --- a/core/trino-main/src/main/java/io/trino/util/CompilerUtils.java +++ b/core/trino-main/src/main/java/io/trino/util/CompilerUtils.java @@ -17,6 +17,8 @@ import io.airlift.bytecode.DynamicClassLoader; import io.airlift.bytecode.ParameterizedType; import io.airlift.log.Logger; +import io.trino.spi.TrinoException; +import org.objectweb.asm.MethodTooLargeException; import java.lang.invoke.MethodHandle; import java.time.Instant; @@ -28,6 +30,7 @@ import static io.airlift.bytecode.BytecodeUtils.toJavaIdentifierString; import static io.airlift.bytecode.ClassGenerator.classGenerator; import static io.airlift.bytecode.ParameterizedType.typeFromJavaClassName; +import static io.trino.spi.StandardErrorCode.QUERY_EXCEEDED_COMPILER_LIMIT; import static java.time.ZoneOffset.UTC; public final class CompilerUtils @@ -78,6 +81,11 @@ public static Class defineClass(ClassDefinition classDefinition public static Class defineClass(ClassDefinition classDefinition, Class superType, DynamicClassLoader classLoader) { log.debug("Defining class: %s", classDefinition.getName()); - return classGenerator(classLoader).defineClass(classDefinition, superType); + try { + return classGenerator(classLoader).defineClass(classDefinition, superType); + } + catch (MethodTooLargeException e) { + throw new TrinoException(QUERY_EXCEEDED_COMPILER_LIMIT, "Query exceeded maximum method size.", e); + } } } diff --git a/core/trino-main/src/main/java/io/trino/util/DateTimeUtils.java b/core/trino-main/src/main/java/io/trino/util/DateTimeUtils.java index b364630f4a4e..e1ac2dd1e266 100644 --- a/core/trino-main/src/main/java/io/trino/util/DateTimeUtils.java +++ b/core/trino-main/src/main/java/io/trino/util/DateTimeUtils.java @@ -408,8 +408,6 @@ public int parseInto(ReadWritablePeriod period, String text, int position, Local int bestValidPos = position; ReadWritablePeriod bestValidPeriod = null; - int bestInvalidPos = position; - for (PeriodParser parser : parsers) { ReadWritablePeriod parsedPeriod = new MutablePeriod(); int parsePos = parser.parseInto(parsedPeriod, text, position, locale); @@ -422,23 +420,13 @@ public int parseInto(ReadWritablePeriod period, String text, int position, Local } } } - else if (parsePos < 0) { - parsePos = ~parsePos; - if (parsePos > bestInvalidPos) { - bestInvalidPos = parsePos; - } - } } - if (bestValidPos > position || (bestValidPos == position)) { - // Restore the state to the best valid parse. - if (bestValidPeriod != null) { - period.setPeriod(bestValidPeriod); - } - return bestValidPos; + // Restore the state to the best valid parse. + if (bestValidPeriod != null) { + period.setPeriod(bestValidPeriod); } - - return ~bestInvalidPos; + return bestValidPos; } } } 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 345ed0c580f4..d62355323467 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 @@ -88,7 +88,6 @@ import io.trino.spi.connector.TableScanRedirectApplicationResult; import io.trino.spi.connector.TopNApplicationResult; import io.trino.spi.connector.WriterScalingOptions; -import io.trino.spi.eventlistener.EventListener; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.function.BoundSignature; import io.trino.spi.function.FunctionDependencyDeclaration; @@ -174,7 +173,6 @@ public class MockConnector private final BiFunction> getSupportedType; private final BiFunction getTableProperties; private final BiFunction> listTablePrivileges; - private final Supplier> eventListeners; private final Collection functions; private final MockConnectorFactory.ListRoleGrants roleGrants; private final Optional partitioningProvider; @@ -229,7 +227,6 @@ public class MockConnector BiFunction> getSupportedType, BiFunction getTableProperties, BiFunction> listTablePrivileges, - Supplier> eventListeners, Collection functions, ListRoleGrants roleGrants, Optional partitioningProvider, @@ -282,7 +279,6 @@ public class MockConnector this.getSupportedType = requireNonNull(getSupportedType, "getSupportedType is null"); this.getTableProperties = requireNonNull(getTableProperties, "getTableProperties is null"); this.listTablePrivileges = requireNonNull(listTablePrivileges, "listTablePrivileges is null"); - this.eventListeners = requireNonNull(eventListeners, "eventListeners is null"); this.functions = ImmutableList.copyOf(functions); this.roleGrants = requireNonNull(roleGrants, "roleGrants is null"); this.partitioningProvider = requireNonNull(partitioningProvider, "partitioningProvider is null"); @@ -321,7 +317,7 @@ public ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLevel @Override public ConnectorMetadata getMetadata(ConnectorSession session, ConnectorTransactionHandle transaction) { - return metadataWrapper.apply(new MockConnectorMetadata(allowSplittingReadIntoMultipleSubQueries)); + return metadataWrapper.apply(new MockConnectorMetadata()); } @Override @@ -367,12 +363,6 @@ public ConnectorNodePartitioningProvider getNodePartitioningProvider() return partitioningProvider.orElseThrow(UnsupportedOperationException::new); } - @Override - public Iterable getEventListeners() - { - return eventListeners.get(); - } - @Override public ConnectorAccessControl getAccessControl() { @@ -448,13 +438,6 @@ public Set getCapabilities() private class MockConnectorMetadata implements ConnectorMetadata { - private final boolean allowSplittingReadIntoMultipleSubQueries; - - public MockConnectorMetadata(boolean allowSplittingReadIntoMultipleSubQueries) - { - this.allowSplittingReadIntoMultipleSubQueries = allowSplittingReadIntoMultipleSubQueries; - } - @Override public boolean schemaExists(ConnectorSession session, String schemaName) { @@ -876,7 +859,7 @@ public ColumnHandle getMergeRowIdColumnHandle(ConnectorSession session, Connecto } @Override - public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, RetryMode retryMode) + public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, Map> updateCaseColumns, RetryMode retryMode) { return new MockConnectorMergeTableHandle((MockConnectorTableHandle) tableHandle); } 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 7a0c8e090360..2ff7a826f9fb 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 @@ -53,7 +53,6 @@ import io.trino.spi.connector.TableScanRedirectApplicationResult; import io.trino.spi.connector.TopNApplicationResult; import io.trino.spi.connector.WriterScalingOptions; -import io.trino.spi.eventlistener.EventListener; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.function.FunctionMetadata; import io.trino.spi.function.FunctionProvider; @@ -125,7 +124,6 @@ public class MockConnectorFactory private final BiFunction> getSupportedType; private final BiFunction getTableProperties; private final BiFunction> listTablePrivileges; - private final Supplier> eventListeners; private final Collection functions; private final Function>> data; private final Function metrics; @@ -183,7 +181,6 @@ private MockConnectorFactory( BiFunction> getSupportedType, BiFunction getTableProperties, BiFunction> listTablePrivileges, - Supplier> eventListeners, Collection functions, Function>> data, Function metrics, @@ -237,7 +234,6 @@ private MockConnectorFactory( this.getSupportedType = requireNonNull(getSupportedType, "getSupportedType is null"); this.getTableProperties = requireNonNull(getTableProperties, "getTableProperties is null"); this.listTablePrivileges = requireNonNull(listTablePrivileges, "listTablePrivileges is null"); - this.eventListeners = requireNonNull(eventListeners, "eventListeners is null"); this.functions = ImmutableList.copyOf(functions); this.analyzeProperties = requireNonNull(analyzeProperties, "analyzeProperties is null"); this.schemaProperties = requireNonNull(schemaProperties, "schemaProperties is null"); @@ -301,7 +297,6 @@ public Connector create(String catalogName, Map config, Connecto getSupportedType, getTableProperties, listTablePrivileges, - eventListeners, functions, roleGrants, partitioningProvider, @@ -446,7 +441,6 @@ public static final class Builder private BiFunction> getSupportedType = (session, type) -> Optional.empty(); private BiFunction getTableProperties = defaultGetTableProperties(); private BiFunction> listTablePrivileges = defaultListTablePrivileges(); - private Supplier> eventListeners = ImmutableList::of; private Collection functions = ImmutableList.of(); private ApplyTopN applyTopN = (session, handle, topNCount, sortItems, assignments) -> Optional.empty(); private ApplyFilter applyFilter = (session, handle, constraint) -> Optional.empty(); @@ -683,22 +677,6 @@ public Builder withListTablePrivileges(BiFunction listener); - return this; - } - - public Builder withEventListener(Supplier listenerFactory) - { - requireNonNull(listenerFactory, "listenerFactory is null"); - - this.eventListeners = () -> ImmutableList.of(listenerFactory.get()); - return this; - } - public Builder withFunctions(Collection functions) { requireNonNull(functions, "functions is null"); @@ -882,7 +860,6 @@ public MockConnectorFactory build() getSupportedType, getTableProperties, listTablePrivileges, - eventListeners, functions, data, metrics, diff --git a/core/trino-main/src/test/java/io/trino/eventlistener/TestConnectorEventListener.java b/core/trino-main/src/test/java/io/trino/eventlistener/TestConnectorEventListener.java deleted file mode 100644 index 18c4fda71301..000000000000 --- a/core/trino-main/src/test/java/io/trino/eventlistener/TestConnectorEventListener.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.eventlistener; - -import com.google.common.collect.ImmutableMap; -import com.google.inject.Key; -import io.trino.connector.MockConnectorFactory; -import io.trino.connector.MockConnectorPlugin; -import io.trino.spi.eventlistener.EventListener; -import io.trino.testing.QueryRunner; -import io.trino.testing.StandaloneQueryRunner; -import org.junit.jupiter.api.Test; - -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Supplier; - -import static io.trino.testing.TestingSession.testSessionBuilder; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -public class TestConnectorEventListener -{ - @Test - public void testConnectorWithoutEventListener() - { - QueryRunner queryRunner = new StandaloneQueryRunner(testSessionBuilder().build()); - - queryRunner.getCoordinator().getInstance(Key.get(EventListenerManager.class)).loadEventListeners(); - - assertThatCode(() -> queryRunner.execute("SELECT 1")) - .doesNotThrowAnyException(); - } - - @Test - public void testConnectorWithEventListener() - { - MockEventListenerFactory listenerFactory = new MockEventListenerFactory(); - QueryRunner queryRunner = new StandaloneQueryRunner(testSessionBuilder().build()); - queryRunner.installPlugin(new MockConnectorPlugin(MockConnectorFactory.builder() - .withEventListener(listenerFactory) - .build())); - queryRunner.createCatalog("event_listening", "mock", ImmutableMap.of()); - - queryRunner.getCoordinator().getInstance(Key.get(EventListenerManager.class)).loadEventListeners(); - - assertThat(listenerFactory.getEventListenerInvocationCounter).hasValue(1); - } - - private static class MockEventListenerFactory - implements Supplier - { - private final AtomicLong getEventListenerInvocationCounter = new AtomicLong(0); - - @Override - public EventListener get() - { - getEventListenerInvocationCounter.incrementAndGet(); - return new EventListener() {}; - } - } -} diff --git a/core/trino-main/src/test/java/io/trino/execution/MockManagedQueryExecution.java b/core/trino-main/src/test/java/io/trino/execution/MockManagedQueryExecution.java index 9b3d998c0540..4029f63085bc 100644 --- a/core/trino-main/src/test/java/io/trino/execution/MockManagedQueryExecution.java +++ b/core/trino-main/src/test/java/io/trino/execution/MockManagedQueryExecution.java @@ -136,6 +136,7 @@ public BasicQueryInfo getBasicQueryInfo() DataSize.ofBytes(13), DataSize.ofBytes(13), DataSize.ofBytes(13), + DataSize.ofBytes(13), 16.0, 17.0, memoryUsage, @@ -146,6 +147,10 @@ public BasicQueryInfo getBasicQueryInfo() new Duration(21, NANOSECONDS), new Duration(22, NANOSECONDS), new Duration(23, NANOSECONDS), + new Duration(24, NANOSECONDS), + new Duration(25, NANOSECONDS), + new Duration(26, NANOSECONDS), + new Duration(27, NANOSECONDS), false, ImmutableSet.of(), OptionalDouble.empty(), diff --git a/core/trino-main/src/test/java/io/trino/execution/executor/timesharing/SimulationController.java b/core/trino-main/src/test/java/io/trino/execution/executor/timesharing/SimulationController.java index 990a351170e9..eb268d329241 100644 --- a/core/trino-main/src/test/java/io/trino/execution/executor/timesharing/SimulationController.java +++ b/core/trino-main/src/test/java/io/trino/execution/executor/timesharing/SimulationController.java @@ -16,6 +16,7 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; +import io.airlift.log.Logger; import io.trino.execution.StageId; import io.trino.execution.TaskId; import io.trino.execution.executor.timesharing.SimulationTask.IntermediateTask; @@ -35,6 +36,8 @@ class SimulationController { + private static final Logger log = Logger.get(SimulationController.class); + private static final int DEFAULT_MIN_SPLITS_PER_TASK = 3; private final TimeSharingTaskExecutor taskExecutor; @@ -63,7 +66,7 @@ public synchronized void addTaskSpecification(TaskSpecification spec) public synchronized void clearPendingQueue() { - System.out.println("Clearing pending queue.."); + log.info("Clearing pending queue.."); clearPendingQueue.set(true); } @@ -116,7 +119,7 @@ private synchronized void scheduleSplitsForRunningTasks() return; } - System.out.println("Cleared pending queue."); + log.info("Clearing pending queue."); clearPendingQueue.set(false); } @@ -151,9 +154,7 @@ private synchronized void replaceCompletedTasks() if (specification.getTotalTasks().isPresent() && specificationEnabled.get(specification) && specification.getTotalTasks().getAsInt() <= completedTasks.get(specification).size() + runningTasks.get(specification).size()) { - System.out.println(); - System.out.println(specification.getName() + " disabled for reaching target count " + specification.getTotalTasks()); - System.out.println(); + log.info("\n%s disabled for reaching target count %s\n", specification.getName(), specification.getTotalTasks()); disableSpecification(specification); continue; } 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 d16cee4e9c96..0662bc263cb8 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 @@ -241,7 +241,8 @@ public void testAdd() 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(); + assertThat(actual.getInfo()).isInstanceOf(SplitOperatorInfo.class); + assertThat(((SplitOperatorInfo) actual.getInfo()).getSplitInfo().get("some_info")).isEqualTo("some_value (2 more)"); } @Test 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 59ad5770b07e..b0dd6f4f760e 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 @@ -363,11 +363,11 @@ private void assertPercentileWithinError(String type, SqlVarbinary binary, doubl // Check that the chosen quantile is within the upper and lower bound of the error assertThat(assertions.expression( format("value_at_quantile(CAST(a AS qdigest(%s)), %s) >= %s", type, percentile, lowerBound)) - .binding("a", "X'%s'".formatted(binary.toString().replaceAll("\\s+", " ")))) + .binding("a", "X'%s'".formatted(binary.toHexString().replaceAll("\\s+", " ")))) .isEqualTo(true); assertThat(assertions.expression( format("value_at_quantile(CAST(a AS qdigest(%s)), %s) <= %s", type, percentile, upperBound)) - .binding("a", "X'%s'".formatted(binary.toString().replaceAll("\\s+", " ")))) + .binding("a", "X'%s'".formatted(binary.toHexString().replaceAll("\\s+", " ")))) .isEqualTo(true); } @@ -384,7 +384,7 @@ private void assertPercentilesWithinError(String type, SqlVarbinary binary, doub type, ARRAY_JOINER.join(boxedPercentiles), ARRAY_JOINER.join(lowerBounds))) - .binding("a", "X'%s'".formatted(binary.toString().replaceAll("\\s+", " ")))) + .binding("a", "X'%s'".formatted(binary.toHexString().replaceAll("\\s+", " ")))) .hasType(new ArrayType(BOOLEAN)) .isEqualTo(Collections.nCopies(percentiles.length, true)); @@ -395,7 +395,7 @@ private void assertPercentilesWithinError(String type, SqlVarbinary binary, doub type, ARRAY_JOINER.join(boxedPercentiles), ARRAY_JOINER.join(upperBounds))) - .binding("a", "X'%s'".formatted(binary.toString().replaceAll("\\s+", " ")))) + .binding("a", "X'%s'".formatted(binary.toHexString().replaceAll("\\s+", " ")))) .hasType(new ArrayType(BOOLEAN)) .isEqualTo(Collections.nCopies(percentiles.length, true)); } 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 e029d8f0496c..d12931da47f4 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 @@ -140,6 +140,6 @@ private static void addAll(QuantileDigest digest, long... values) private static String toHexString(QuantileDigest qdigest) { - return new SqlVarbinary(qdigest.serialize().getBytes()).toString().replaceAll("\\s+", " "); + return new SqlVarbinary(qdigest.serialize().getBytes()).toHexString().replaceAll("\\s+", " "); } } diff --git a/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryDataSerialization.java b/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryDataSerialization.java index d1beddc5ca75..980c9844652a 100644 --- a/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryDataSerialization.java +++ b/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryDataSerialization.java @@ -245,14 +245,19 @@ private String queryResultsJson(String expectedDataField) "queuedSplits": 0, "runningSplits": 0, "completedSplits": 0, + "planningTimeMillis": 0, + "analysisTimeMillis": 0, "cpuTimeMillis": 0, "wallTimeMillis": 0, "queuedTimeMillis": 0, "elapsedTimeMillis": 0, + "finishingTimeMillis": 0, + "physicalInputTimeMillis": 0, "processedRows": 0, "processedBytes": 0, "physicalInputBytes": 0, "physicalWrittenBytes": 0, + "internalNetworkInputBytes": 0, "peakMemoryBytes": 0, "spilledBytes": 0 }, diff --git a/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryResultsSerialization.java b/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryResultsSerialization.java index 56fc7a5cad13..badc453a165e 100644 --- a/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryResultsSerialization.java +++ b/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryResultsSerialization.java @@ -77,14 +77,19 @@ public void testNullDataSerialization() "queuedSplits" : 0, "runningSplits" : 0, "completedSplits" : 0, + "planningTimeMillis": 0, + "analysisTimeMillis": 0, "cpuTimeMillis" : 0, "wallTimeMillis" : 0, "queuedTimeMillis" : 0, "elapsedTimeMillis" : 0, + "finishingTimeMillis": 0, + "physicalInputTimeMillis": 0, "processedRows" : 0, "processedBytes" : 0, "physicalInputBytes" : 0, "physicalWrittenBytes" : 0, + "internalNetworkInputBytes": 0, "peakMemoryBytes" : 0, "spilledBytes" : 0 }, @@ -153,14 +158,19 @@ private String queryResultsJson(String expectedDataField) "queuedSplits" : 0, "runningSplits" : 0, "completedSplits" : 0, + "planningTimeMillis": 0, + "analysisTimeMillis": 0, "cpuTimeMillis" : 0, "wallTimeMillis" : 0, "queuedTimeMillis" : 0, "elapsedTimeMillis" : 0, + "finishingTimeMillis": 0, + "physicalInputTimeMillis": 0, "processedRows" : 0, "processedBytes" : 0, "physicalInputBytes" : 0, "physicalWrittenBytes" : 0, + "internalNetworkInputBytes": 0, "peakMemoryBytes" : 0, "spilledBytes" : 0 }, diff --git a/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestingHydraIdentityProvider.java b/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestingHydraIdentityProvider.java index 9d910dae3ce3..c16fad53035b 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestingHydraIdentityProvider.java +++ b/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestingHydraIdentityProvider.java @@ -71,7 +71,7 @@ public class TestingHydraIdentityProvider implements AutoCloseable { - private static final String HYDRA_IMAGE = "oryd/hydra:v1.10.6"; + private static final String HYDRA_IMAGE = "oryd/hydra:v1.11.10"; private static final String ISSUER = "https://localhost:4444/"; private static final String DSN = "postgres://hydra:mysecretpassword@database:5432/hydra?sslmode=disable"; diff --git a/core/trino-parser/pom.xml b/core/trino-parser/pom.xml index fbf13b76bc9c..55a765673a7c 100644 --- a/core/trino-parser/pom.xml +++ b/core/trino-parser/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/LongLiteral.java b/core/trino-parser/src/main/java/io/trino/sql/tree/LongLiteral.java index 0ef8289ccbcd..17591d94ee29 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/tree/LongLiteral.java +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/LongLiteral.java @@ -107,12 +107,21 @@ private static long parse(String value) if (value.startsWith("0x") || value.startsWith("0X")) { return Long.parseLong(value.substring(2), 16); } + else if (value.startsWith("-0x") || value.startsWith("-0X")) { + return Long.parseLong("-" + value.substring(3), 16); + } else if (value.startsWith("0b") || value.startsWith("0B")) { return Long.parseLong(value.substring(2), 2); } + else if (value.startsWith("-0b") || value.startsWith("-0B")) { + return Long.parseLong("-" + value.substring(3), 2); + } else if (value.startsWith("0o") || value.startsWith("0O")) { return Long.parseLong(value.substring(2), 8); } + else if (value.startsWith("-0o") || value.startsWith("-0O")) { + return Long.parseLong("-" + value.substring(3), 8); + } else { return Long.parseLong(value); } 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 dcc7e3f4b927..9b5196cf1d8a 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 @@ -486,6 +486,14 @@ public void testNumbers() .isEqualTo(new LongLiteral(new NodeLocation(1, 1), "0X123_ABC_DEF")) .satisfies(value -> assertThat(((LongLiteral) value).getParsedValue()).isEqualTo(4893429231L)); + assertThat(expression("-0x123_abc_def")) + .isEqualTo(new LongLiteral(new NodeLocation(1, 1), "-0x123_abc_def")) + .satisfies(value -> assertThat(((LongLiteral) value).getParsedValue()).isEqualTo(-4893429231L)); + + assertThat(expression("-0X123_ABC_DEF")) + .isEqualTo(new LongLiteral(new NodeLocation(1, 1), "-0X123_ABC_DEF")) + .satisfies(value -> assertThat(((LongLiteral) value).getParsedValue()).isEqualTo(-4893429231L)); + assertThatThrownBy(() -> SQL_PARSER.createExpression("0x123_ABC_DEF_")) .isInstanceOf(ParsingException.class); @@ -497,6 +505,14 @@ public void testNumbers() .isEqualTo(new LongLiteral(new NodeLocation(1, 1), "0o012_345")) .satisfies(value -> assertThat(((LongLiteral) value).getParsedValue()).isEqualTo(5349L)); + assertThat(expression("-0O012_345")) + .isEqualTo(new LongLiteral(new NodeLocation(1, 1), "-0O012_345")) + .satisfies(value -> assertThat(((LongLiteral) value).getParsedValue()).isEqualTo(-5349L)); + + assertThat(expression("-0o012_345")) + .isEqualTo(new LongLiteral(new NodeLocation(1, 1), "-0o012_345")) + .satisfies(value -> assertThat(((LongLiteral) value).getParsedValue()).isEqualTo(-5349L)); + assertThatThrownBy(() -> SQL_PARSER.createExpression("0o012_345_")) .isInstanceOf(ParsingException.class); @@ -508,6 +524,14 @@ public void testNumbers() .isEqualTo(new LongLiteral(new NodeLocation(1, 1), "0b110_010")) .satisfies(value -> assertThat(((LongLiteral) value).getParsedValue()).isEqualTo(50L)); + assertThat(expression("-0B110_010")) + .isEqualTo(new LongLiteral(new NodeLocation(1, 1), "-0B110_010")) + .satisfies(value -> assertThat(((LongLiteral) value).getParsedValue()).isEqualTo(-50L)); + + assertThat(expression("-0b110_010")) + .isEqualTo(new LongLiteral(new NodeLocation(1, 1), "-0b110_010")) + .satisfies(value -> assertThat(((LongLiteral) value).getParsedValue()).isEqualTo(-50L)); + assertThatThrownBy(() -> SQL_PARSER.createExpression("0b110_010_")) .isInstanceOf(ParsingException.class); } diff --git a/core/trino-server-main/pom.xml b/core/trino-server-main/pom.xml index b7f011bca4e4..62098b8116e3 100644 --- a/core/trino-server-main/pom.xml +++ b/core/trino-server-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index 82c2b0828f92..ef18e071218f 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-rpm/src/main/rpm/postinstall b/core/trino-server-rpm/src/main/rpm/postinstall index 3764fa7455c3..f0261473f117 100644 --- a/core/trino-server-rpm/src/main/rpm/postinstall +++ b/core/trino-server-rpm/src/main/rpm/postinstall @@ -10,16 +10,6 @@ install --directory --mode=755 /var/log/trino # Populate node.id from uuidgen by replacing template with the node uuid sed -i "s/\$(uuid-generated-nodeid)/$(cat /proc/sys/kernel/random/uuid)/g" /etc/trino/node.properties -# read /tmp/trino-rpm-install-java-home created during pre-install and save JAVA_HOME in env.sh at Trino config location -if [ -r /tmp/trino-rpm-install-java-home ]; then - JAVA_HOME=$(cat /tmp/trino-rpm-install-java-home) - target=/etc/trino/env.sh - sed -i "/^#JAVA_HOME=$/d" $target - if ! grep -q '^JAVA_HOME=' $target >/dev/null; then - echo "JAVA_HOME=$JAVA_HOME" >> $target - fi -fi - chown -R trino:trino /var/lib/trino chown -R trino:trino /var/log/trino chown -R trino:trino /etc/trino diff --git a/core/trino-server-rpm/src/main/rpm/preinstall b/core/trino-server-rpm/src/main/rpm/preinstall index d0a7e90d2c21..4ac7fc7a6fa7 100644 --- a/core/trino-server-rpm/src/main/rpm/preinstall +++ b/core/trino-server-rpm/src/main/rpm/preinstall @@ -1,72 +1,4 @@ # Pre installation script -# Ensure that the proper version of Java exists on the system - -java_version() { - # The one argument is the location of java (either $JAVA_HOME or a potential - # candidate for JAVA_HOME. - JAVA="$1"/bin/java - "$JAVA" -version 2>&1 | grep "\(java\|openjdk\) version" | awk '{ print substr($3, 2, length($3)-2); }' -} - -check_if_correct_java_version() { - - # If the string is empty return non-zero code. We don't want false positives if /bin/java is - # a valid java version because that will leave JAVA_HOME unset and the init.d scripts will - # use the default java version, which may not be the correct version. - if [ -z "$1" ]; then - return 1 - fi - - # The one argument is the location of java (either $JAVA_HOME or a potential - # candidate for JAVA_HOME). - JAVA_VERSION=$(java_version "$1") - JAVA_MAJOR=$(echo "$JAVA_VERSION" | cut -d'-' -f1 | cut -d'.' -f1) - if [ "$JAVA_MAJOR" -ge "23" ]; then - echo "$1" >/tmp/trino-rpm-install-java-home - return 0 - else - return 1 - fi -} - -# if Java version of $JAVA_HOME is not correct, then try to find it again below -if ! check_if_correct_java_version "$JAVA_HOME"; then - java_found=false - for candidate in \ - /usr/lib/jvm/java-23-* \ - /usr/lib/jvm/zulu-23 \ - /usr/lib/jvm/temurin-23 \ - /usr/lib/jvm/temurin-23-* \ - /usr/lib/jvm/default-java \ - /usr/java/default \ - / \ - /usr; do - if [ -e "$candidate"/bin/java ]; then - if check_if_correct_java_version "$candidate"; then - java_found=true - break - fi - fi - done -fi - -# if no appropriate java found -if [ "$java_found" = false ]; then - cat 1>&2 </dev/null || /usr/sbin/groupadd -r trino getent passwd trino >/dev/null || /usr/sbin/useradd --comment "Trino" -s /sbin/nologin -g trino -r -d /var/lib/trino trino 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 93e89aebfef5..946d3734883b 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 @@ -109,6 +109,7 @@ private void testInstall(String temurinReleaseName, String javaHome, String expe .withCommand("sh", "-xeuc", command) .withCreateContainerCmdModifier(modifier -> modifier .withHostConfig(modifier.getHostConfig().withInit(true))) + .withEnv("JAVA_HOME", javaHome) .waitingFor(forLogMessage(".*SERVER STARTED.*", 1).withStartupTimeout(Duration.ofMinutes(5))) .start(); QueryRunner queryRunner = new QueryRunner(container.getHost(), container.getMappedPort(8080)); @@ -137,6 +138,7 @@ private void testUninstall(String temurinReleaseName, String javaHome) try (GenericContainer container = new GenericContainer<>(BASE_IMAGE)) { container.withFileSystemBind(rpmHostPath, rpm, BindMode.READ_ONLY) .withCommand("sh", "-xeuc", installAndStartTrino) + .withEnv("JAVA_HOME", javaHome) .withCreateContainerCmdModifier(modifier -> modifier .withHostConfig(modifier.getHostConfig().withInit(true))) .waitingFor(forLogMessage(".*SERVER STARTED.*", 1).withStartupTimeout(Duration.ofMinutes(5))) diff --git a/core/trino-server/pom.xml b/core/trino-server/pom.xml index 1fb9b5ba82c5..1b611ff11e6b 100644 --- a/core/trino-server/pom.xml +++ b/core/trino-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 57fb4b73f050..e6358e816586 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -214,46 +214,65 @@ true - java.method.returnTypeTypeParametersChanged - method java.lang.Iterable<io.trino.spi.protocol.SpoolingManagerFactory> io.trino.spi.Plugin::getSpoolingManagerFactories() - method java.lang.Iterable<io.trino.spi.spool.SpoolingManagerFactory> io.trino.spi.Plugin::getSpoolingManagerFactories() - Spooling package renamed + java.method.removed + method java.lang.Iterable<io.trino.spi.eventlistener.EventListener> io.trino.spi.connector.Connector::getEventListeners() + Remove connector event listeners true - java.class.removed - interface io.trino.spi.protocol.SpoolingManager - Spooling package renamed + java.annotation.removed + method byte[] io.trino.spi.type.SqlVarbinary::getBytes() + method byte[] io.trino.spi.type.SqlVarbinary::getBytes() + @com.fasterxml.jackson.annotation.JsonValue + On-the-wire representation shouldn't rely on the Jackson format - java.method.noLongerDefault - method io.trino.spi.security.SystemAccessControl io.trino.spi.security.SystemAccessControlFactory::create(java.util.Map<java.lang.String, java.lang.String>, io.trino.spi.security.SystemAccessControlFactory.SystemAccessControlContext) - method io.trino.spi.security.SystemAccessControl io.trino.spi.security.SystemAccessControlFactory::create(java.util.Map<java.lang.String, java.lang.String>, io.trino.spi.security.SystemAccessControlFactory.SystemAccessControlContext) - Old variant was removed and this becomes the new non-default one + true + java.annotation.removed + method java.lang.String io.trino.spi.type.SqlDate::toString() + method java.lang.String io.trino.spi.type.SqlDate::toString() + @com.fasterxml.jackson.annotation.JsonValue + On-the-wire representation shouldn't rely on the Jackson format - java.method.nowAbstract - method io.trino.spi.security.SystemAccessControl io.trino.spi.security.SystemAccessControlFactory::create(java.util.Map<java.lang.String, java.lang.String>, io.trino.spi.security.SystemAccessControlFactory.SystemAccessControlContext) - method io.trino.spi.security.SystemAccessControl io.trino.spi.security.SystemAccessControlFactory::create(java.util.Map<java.lang.String, java.lang.String>, io.trino.spi.security.SystemAccessControlFactory.SystemAccessControlContext) - Old variant was removed and this becomes the new non-default one + true + java.annotation.removed + method java.lang.String io.trino.spi.type.SqlDecimal::toString() + method java.lang.String io.trino.spi.type.SqlDecimal::toString() + @com.fasterxml.jackson.annotation.JsonValue + On-the-wire representation shouldn't rely on the Jackson format true - java.method.numberOfParametersChanged - method io.trino.spi.eventlistener.EventListener io.trino.spi.eventlistener.EventListenerFactory::create(java.util.Map<java.lang.String, java.lang.String>) - method io.trino.spi.eventlistener.EventListener io.trino.spi.eventlistener.EventListenerFactory::create(java.util.Map<java.lang.String, java.lang.String>, io.trino.spi.eventlistener.EventListenerFactory.EventListenerContext) - Added EventListenerContext + java.annotation.removed + method java.lang.String io.trino.spi.type.SqlTime::toString() + method java.lang.String io.trino.spi.type.SqlTime::toString() + @com.fasterxml.jackson.annotation.JsonValue + On-the-wire representation shouldn't rely on the Jackson format - java.method.removed - method io.trino.spi.connector.ConnectorTableHandle io.trino.spi.connector.ConnectorMetadata::makeCompatiblePartitioning(io.trino.spi.connector.ConnectorSession, io.trino.spi.connector.ConnectorTableHandle, io.trino.spi.connector.ConnectorPartitioningHandle) + true + java.annotation.removed + method java.lang.String io.trino.spi.type.SqlTimeWithTimeZone::toString() + method java.lang.String io.trino.spi.type.SqlTimeWithTimeZone::toString() + @com.fasterxml.jackson.annotation.JsonValue + On-the-wire representation shouldn't rely on the Jackson format true - java.method.numberOfParametersChanged - method void io.trino.spi.eventlistener.QueryStatistics::<init>(java.time.Duration, java.time.Duration, java.time.Duration, java.time.Duration, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, double, double, java.util.List<io.trino.spi.eventlistener.StageGcStatistics>, int, boolean, java.util.List<io.trino.spi.eventlistener.StageCpuDistribution>, java.util.List<io.trino.spi.eventlistener.StageOutputBufferUtilization>, java.util.List<io.trino.spi.eventlistener.StageTaskStatistics>, java.util.function.Supplier<java.util.List<java.lang.String>>, java.util.List<io.trino.spi.eventlistener.QueryPlanOptimizerStatistics>, java.util.Optional<java.lang.String>) - method void io.trino.spi.eventlistener.QueryStatistics::<init>(java.time.Duration, java.time.Duration, java.time.Duration, java.time.Duration, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, java.util.Optional<java.time.Duration>, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, long, double, double, java.util.List<io.trino.spi.eventlistener.StageGcStatistics>, int, boolean, java.util.List<io.trino.spi.eventlistener.StageCpuDistribution>, java.util.List<io.trino.spi.eventlistener.StageOutputBufferUtilization>, java.util.List<io.trino.spi.eventlistener.StageOutputBufferMetrics>, java.util.List<io.trino.spi.eventlistener.StageTaskStatistics>, java.util.function.Supplier<java.util.List<java.lang.String>>, java.util.List<io.trino.spi.eventlistener.QueryPlanOptimizerStatistics>, java.util.Optional<java.lang.String>) - Added output buffer metrics + java.annotation.removed + method java.lang.String io.trino.spi.type.SqlTimestamp::toString() + method java.lang.String io.trino.spi.type.SqlTimestamp::toString() + @com.fasterxml.jackson.annotation.JsonValue + On-the-wire representation shouldn't rely on the Jackson format + + + true + java.annotation.removed + method java.lang.String io.trino.spi.type.SqlTimestampWithTimeZone::toString() + method java.lang.String io.trino.spi.type.SqlTimestampWithTimeZone::toString() + @com.fasterxml.jackson.annotation.JsonValue + On-the-wire representation shouldn't rely on the Jackson format 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 a6bf5755b44b..6273a335f64b 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 @@ -14,7 +14,6 @@ package io.trino.spi.connector; import io.trino.spi.Experimental; -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; @@ -231,14 +230,6 @@ default ConnectorAccessControl getAccessControl() throw new UnsupportedOperationException(); } - /** - * @return the event listeners provided by this connector - */ - default Iterable getEventListeners() - { - return emptySet(); - } - /** * Commit the transaction. Will be called at most once and will not be called if * {@link #rollback} is called. 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 4696d8c03ea4..ce45ca7415f0 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 @@ -116,29 +116,6 @@ default ConnectorTableHandle getTableHandle( throw new TrinoException(GENERIC_INTERNAL_ERROR, "ConnectorMetadata getTableHandle() is not implemented"); } - /** - * Create initial handle for execution of table procedure. The handle will be used through planning process. It will be converted to final - * handle used for execution via @{link {@link ConnectorMetadata#beginTableExecute} - *

- * If connector does not support execution with retries, the method should throw: - *

-     *     new TrinoException(NOT_SUPPORTED, "This connector does not support query retries")
-     * 
- * unless {@code retryMode} is set to {@code NO_RETRIES}. - * - * @deprecated {Use {@link #getTableHandleForExecute(ConnectorSession, ConnectorAccessControl, ConnectorTableHandle, String, Map, RetryMode)}} - */ - @Deprecated - default Optional getTableHandleForExecute( - ConnectorSession session, - ConnectorTableHandle tableHandle, - String procedureName, - Map executeProperties, - RetryMode retryMode) - { - throw new TrinoException(NOT_SUPPORTED, "This connector does not support table procedures"); - } - /** * Create initial handle for execution of table procedure. The handle will be used through planning process. It will be converted to final * handle used for execution via @{link {@link ConnectorMetadata#beginTableExecute} @@ -157,7 +134,7 @@ default Optional getTableHandleForExecute( Map executeProperties, RetryMode retryMode) { - return getTableHandleForExecute(session, tableHandle, procedureName, executeProperties, retryMode); + throw new TrinoException(NOT_SUPPORTED, "This connector does not support table procedures"); } default Optional getLayoutForTableExecute(ConnectorSession session, ConnectorTableExecuteHandle tableExecuteHandle) @@ -885,25 +862,13 @@ default Optional getUpdateLayout(ConnectorSession s return Optional.empty(); } - /** - * Do whatever is necessary to start an MERGE query, returning the {@link ConnectorMergeTableHandle} - * instance that will be passed to the PageSink, and to the {@link #finishMerge} method. - * - * @deprecated {Use {@link #beginMerge(ConnectorSession, ConnectorTableHandle, Map, RetryMode)}} - */ - @Deprecated - default ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, RetryMode retryMode) - { - throw new TrinoException(NOT_SUPPORTED, MODIFYING_ROWS_MESSAGE); - } - /** * Do whatever is necessary to start an MERGE query, returning the {@link ConnectorMergeTableHandle} * instance that will be passed to the PageSink, and to the {@link #finishMerge} method. */ default ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, Map> updateCaseColumns, RetryMode retryMode) { - return beginMerge(session, tableHandle, retryMode); + throw new TrinoException(NOT_SUPPORTED, MODIFYING_ROWS_MESSAGE); } /** diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/SqlDate.java b/core/trino-spi/src/main/java/io/trino/spi/type/SqlDate.java index d8f092448de3..2ca4cecab692 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/SqlDate.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/SqlDate.java @@ -13,8 +13,6 @@ */ package io.trino.spi.type; -import com.fasterxml.jackson.annotation.JsonValue; - import java.time.LocalDate; public final class SqlDate @@ -51,7 +49,6 @@ public boolean equals(Object obj) return days == other.days; } - @JsonValue @Override public String toString() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/SqlDecimal.java b/core/trino-spi/src/main/java/io/trino/spi/type/SqlDecimal.java index f84341736ad6..d25369fe6782 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/SqlDecimal.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/SqlDecimal.java @@ -13,8 +13,6 @@ */ package io.trino.spi.type; -import com.fasterxml.jackson.annotation.JsonValue; - import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; @@ -81,7 +79,6 @@ public int hashCode() return Objects.hash(unscaledValue, precision, scale); } - @JsonValue @Override public String toString() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/SqlTime.java b/core/trino-spi/src/main/java/io/trino/spi/type/SqlTime.java index a70a50a1875b..44113f652cb0 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/SqlTime.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/SqlTime.java @@ -13,8 +13,6 @@ */ package io.trino.spi.type; -import com.fasterxml.jackson.annotation.JsonValue; - import java.util.Objects; import static io.trino.spi.type.TimeType.MAX_PRECISION; @@ -85,7 +83,6 @@ public int hashCode() return Objects.hash(precision, picos); } - @JsonValue @Override public String toString() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimeWithTimeZone.java b/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimeWithTimeZone.java index dc039a09bf4a..c37c18a32b6c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimeWithTimeZone.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimeWithTimeZone.java @@ -13,8 +13,6 @@ */ package io.trino.spi.type; -import com.fasterxml.jackson.annotation.JsonValue; - import java.util.Objects; import static io.trino.spi.type.TimeType.MAX_PRECISION; @@ -99,7 +97,6 @@ public int hashCode() return Objects.hash(precision, picos, offsetMinutes); } - @JsonValue @Override public String toString() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimestamp.java b/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimestamp.java index bab2fadfda55..1a2b2558adff 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimestamp.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimestamp.java @@ -13,8 +13,6 @@ */ package io.trino.spi.type; -import com.fasterxml.jackson.annotation.JsonValue; - import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Objects; @@ -149,7 +147,6 @@ public int hashCode() return Objects.hash(epochMicros, picosOfMicros, precision); } - @JsonValue @Override public String toString() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimestampWithTimeZone.java b/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimestampWithTimeZone.java index 7c1b6155f5ce..9eaf8133e80c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimestampWithTimeZone.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/SqlTimestampWithTimeZone.java @@ -13,8 +13,6 @@ */ package io.trino.spi.type; -import com.fasterxml.jackson.annotation.JsonValue; - import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -132,7 +130,6 @@ public SqlTimestampWithTimeZone roundTo(int precision) return newInstanceWithRounding(precision, epochMillis, picosOfMilli, timeZoneKey); } - @JsonValue @Override public String toString() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/SqlVarbinary.java b/core/trino-spi/src/main/java/io/trino/spi/type/SqlVarbinary.java index 5c54a95fe15f..673c14fc35f6 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/SqlVarbinary.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/SqlVarbinary.java @@ -13,9 +13,8 @@ */ package io.trino.spi.type; -import com.fasterxml.jackson.annotation.JsonValue; - import java.util.Arrays; +import java.util.Base64; import java.util.HexFormat; import static java.lang.Math.min; @@ -42,7 +41,6 @@ public int compareTo(SqlVarbinary obj) return Arrays.compare(bytes, obj.bytes); } - @JsonValue public byte[] getBytes() { return bytes; @@ -69,6 +67,11 @@ public boolean equals(Object obj) @Override public String toString() + { + return Base64.getEncoder().encodeToString(bytes); + } + + public String toHexString() { if (bytes.length == 0) { return ""; diff --git a/core/trino-spi/src/test/java/io/trino/spi/type/TestSqlVarbinary.java b/core/trino-spi/src/test/java/io/trino/spi/type/TestSqlVarbinary.java index 54da3087a7b6..8d4bc970035a 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/type/TestSqlVarbinary.java +++ b/core/trino-spi/src/test/java/io/trino/spi/type/TestSqlVarbinary.java @@ -15,24 +15,27 @@ import org.junit.jupiter.api.Test; +import java.util.Base64; + import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; public class TestSqlVarbinary { @Test - public void testToString() + public void testToHexString() { for (int lines = 0; lines < 5; lines++) { for (int lastLineBytes = 0; lastLineBytes < 32; lastLineBytes++) { byte[] bytes = createBytes(lines, lastLineBytes); - String expected = simpleToString(bytes); - assertThat(expected).isEqualTo(new SqlVarbinary(bytes).toString()); + String expectedHex = simpleToHex(bytes); + assertThat(expectedHex).isEqualTo(new SqlVarbinary(bytes).toHexString()); + assertThat(Base64.getEncoder().encodeToString(bytes)).isEqualTo(new SqlVarbinary(bytes).toString()); } } } - private static String simpleToString(byte[] bytes) + private static String simpleToHex(byte[] bytes) { StringBuilder builder = new StringBuilder(); diff --git a/core/trino-web-ui/pom.xml b/core/trino-web-ui/pom.xml index 9f21b8edf7d3..a8ef1e605f99 100644 --- a/core/trino-web-ui/pom.xml +++ b/core/trino-web-ui/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index 55aa7e62b4b9..8ad50977d6df 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT trino-docs diff --git a/docs/src/main/sphinx/admin/properties-sql-environment.md b/docs/src/main/sphinx/admin/properties-sql-environment.md index a4092cef9ace..d31ce3f13990 100644 --- a/docs/src/main/sphinx/admin/properties-sql-environment.md +++ b/docs/src/main/sphinx/admin/properties-sql-environment.md @@ -31,15 +31,14 @@ client overrides this default. - **Type:** [](prop-type-string) -Set the default catalog for [SQL routine](/routines) storage for all clients. -The connector used in the catalog must support [](sql-routine-management). Any -usage of a fully qualified name for a routine overrides this default. +Set the default catalog for [](/udf) storage for all clients. The connector used +in the catalog must support [](udf-management). Any usage of a fully qualified +name for a UDF overrides this default. -The default catalog and schema for SQL routine storage must be configured -together, and the resulting entry must be set as part of the path. For example, -the following example section for [](config-properties) uses the `functions` -schema in the `brain` catalog for routine storage, and adds it as the only entry -on the path: +The default catalog and schema for UDF storage must be configured together, and +the resulting entry must be set as part of the path. For example, the following +section for [](config-properties) uses the `functions` schema in the `brain` +catalog for UDF storage, and adds it as the only entry on the path: ```properties sql.default-function-catalog=brain @@ -51,9 +50,9 @@ sql.path=brain.default - **Type:** [](prop-type-string) -Set the default schema for SQL routine storage for all clients. Must be set to a -schema name that is valid for the default function catalog. Any usage of a fully -qualified name for a routine overrides this default. +Set the default schema for UDF storage for all clients. Must be set to a schema +name that is valid for the default function catalog. Any usage of a fully +qualified name for a UDF overrides this default. ## `sql.path` diff --git a/docs/src/main/sphinx/client.md b/docs/src/main/sphinx/client.md index 502bf3552520..98837c87f999 100644 --- a/docs/src/main/sphinx/client.md +++ b/docs/src/main/sphinx/client.md @@ -15,9 +15,10 @@ following client drivers: * [trino-go-client](https://github.com/trinodb/trino-go-client) * [trino-js-client](https://github.com/trinodb/trino-js-client) * [trino-python-client](https://github.com/trinodb/trino-python-client) +* [trino-csharp-client](https://github.com/trinodb/trino-csharp-client) Other communities and vendors provide [other client -drivers](https://trino.io/ecosystem/client.html). +drivers](https://trino.io/ecosystem/client-driver#other-client-drivers). ## Client applications @@ -26,11 +27,12 @@ run queries with Trino. You can inspect the results, perform analytics with further queries, and create visualizations. Client applications typically use a client driver. -The Trino project maintains the [Trino command line interface](/client/cli) as a -client application. +The Trino project maintains the [Trino command line interface](/client/cli) and +the [Trino Grafana Data Source Plugin](https://github.com/trinodb/grafana-trino) +as a client application. Other communities and vendors provide [numerous other client -applications](https://trino.io/ecosystem/client.html) +applications](https://trino.io/ecosystem/client-application#other-client-applications) ## Client protocol diff --git a/docs/src/main/sphinx/client/cli.md b/docs/src/main/sphinx/client/cli.md index 8b7a0ee17613..439d43558d82 100644 --- a/docs/src/main/sphinx/client/cli.md +++ b/docs/src/main/sphinx/client/cli.md @@ -181,8 +181,7 @@ mode: - Do not show query processing progress. * - `--path` - Set the default [SQL path](/sql/set-path) for the session. Useful for - setting a catalog and schema location for [catalog - routines](routine-catalog). + setting a catalog and schema location for [](udf-catalog). * - `--password` - Prompts for a password. Use if your Trino server requires password authentication. You can set the `TRINO_PASSWORD` environment variable with diff --git a/docs/src/main/sphinx/client/jdbc.md b/docs/src/main/sphinx/client/jdbc.md index 427f688000f3..bcf7af7a0182 100644 --- a/docs/src/main/sphinx/client/jdbc.md +++ b/docs/src/main/sphinx/client/jdbc.md @@ -142,7 +142,7 @@ may not be specified using both methods. - Client tags for selecting resource groups. Example: `abc,xyz` * - `path` - Set the default [SQL path](/sql/set-path) for the session. Useful for - setting a catalog and schema location for [catalog routines](routine-catalog). + setting a catalog and schema location for [](udf-catalog). * - `traceToken` - Trace token for correlating requests across systems. * - `source` diff --git a/docs/src/main/sphinx/connector/alter-schema-limitation.fragment b/docs/src/main/sphinx/connector/alter-schema-limitation.fragment index 39065f900324..33100d90c262 100644 --- a/docs/src/main/sphinx/connector/alter-schema-limitation.fragment +++ b/docs/src/main/sphinx/connector/alter-schema-limitation.fragment @@ -1,4 +1,4 @@ -### ALTER SCHEMA +### ALTER SCHEMA limitation The connector supports renaming a schema with the `ALTER SCHEMA RENAME` statement. `ALTER SCHEMA SET AUTHORIZATION` is not supported. diff --git a/docs/src/main/sphinx/connector/alter-table-limitation.fragment b/docs/src/main/sphinx/connector/alter-table-limitation.fragment index de64a02a9c3b..d3101270ee69 100644 --- a/docs/src/main/sphinx/connector/alter-table-limitation.fragment +++ b/docs/src/main/sphinx/connector/alter-table-limitation.fragment @@ -1,4 +1,4 @@ -### ALTER TABLE RENAME TO +### ALTER TABLE RENAME TO limitation The connector does not support renaming tables across multiple schemas. For example, the following statement is supported: diff --git a/docs/src/main/sphinx/connector/clickhouse.md b/docs/src/main/sphinx/connector/clickhouse.md index 0da7a863bb28..fc34ac02ee17 100644 --- a/docs/src/main/sphinx/connector/clickhouse.md +++ b/docs/src/main/sphinx/connector/clickhouse.md @@ -93,9 +93,6 @@ configured connector to create a catalog named `sales`. ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - ## Querying ClickHouse The ClickHouse connector provides a schema for every ClickHouse *database*. @@ -317,18 +314,27 @@ No other types are supported. (clickhouse-sql-support)= ## SQL support -The connector provides read and write access to data and metadata in -a ClickHouse catalog. In addition to the {ref}`globally available -` and {ref}`read operation ` +The connector provides read and write access to data and metadata in a +ClickHouse catalog. In addition to the [globally +available](sql-globally-available) and [read operation](sql-read-operations) statements, the connector supports the following features: -- {doc}`/sql/insert` -- {doc}`/sql/truncate` -- {ref}`sql-schema-table-management` +- [](/sql/insert), see also [](clickhouse-insert) +- [](/sql/truncate) +- [](sql-schema-table-management), see also: + - [](clickhouse-alter-table) +- [](clickhouse-procedures) +- [](clickhouse-table-functions) + +(clickhouse-insert)= +```{include} non-transactional-insert.fragment +``` +(clickhouse-alter-table)= ```{include} alter-schema-limitation.fragment ``` +(clickhouse-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -336,6 +342,7 @@ statements, the connector supports the following features: ```{include} procedures-execute.fragment ``` +(clickhouse-table-functions)= ### Table functions The connector provides specific {doc}`table functions ` to diff --git a/docs/src/main/sphinx/connector/delta-lake.md b/docs/src/main/sphinx/connector/delta-lake.md index 2e6785c3a40a..0069b9fe65af 100644 --- a/docs/src/main/sphinx/connector/delta-lake.md +++ b/docs/src/main/sphinx/connector/delta-lake.md @@ -108,7 +108,7 @@ values. Typical usage does not require you to configure them. * `GZIP` The equivalent catalog session property is `compression_codec`. - - `SNAPPY` + - `ZSTD` * - `delta.max-partitions-per-writer` - Maximum number of partitions per writer. - `100` diff --git a/docs/src/main/sphinx/connector/druid.md b/docs/src/main/sphinx/connector/druid.md index ab6f4d269a2b..261047c85dcd 100644 --- a/docs/src/main/sphinx/connector/druid.md +++ b/docs/src/main/sphinx/connector/druid.md @@ -116,10 +116,15 @@ be an empty string `''`, and so forth. (druid-sql-support)= ## SQL support -The connector provides {ref}`globally available ` and -{ref}`read operation ` statements to access data and -metadata in the Druid database. +The connector provides read access to data and metadata in the Druid database. +In addition to the [globally available](sql-globally-available) and [read +operation](sql-read-operations) statements, the connector supports the following +features: +- [](druid-procedures) +- [](druid-table-functions) + +(druid-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -127,6 +132,7 @@ metadata in the Druid database. ```{include} procedures-execute.fragment ``` +(druid-table-functions)= ### Table functions The connector provides specific {doc}`table functions ` to diff --git a/docs/src/main/sphinx/connector/exasol.md b/docs/src/main/sphinx/connector/exasol.md index a6b7de26d8c2..23147958a70c 100644 --- a/docs/src/main/sphinx/connector/exasol.md +++ b/docs/src/main/sphinx/connector/exasol.md @@ -133,10 +133,15 @@ Exasol does not support longer values. (exasol-sql-support)= ## SQL support -The connector provides {ref}`globally available ` and -{ref}`read operation ` statements to access data and -metadata in the Exasol database. +The connector provides read access to data and metadata in Exasol. In addition +to the [globally available](sql-globally-available) and [read +operation](sql-read-operations) statements, the connector supports the following +features: +- [](exasol-procedures) +- [](exasol-table-functions) + +(exasol-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -144,6 +149,7 @@ metadata in the Exasol database. ```{include} procedures-execute.fragment ``` +(exasol-table-functions)= ### Table functions The connector provides specific {doc}`table functions ` to diff --git a/docs/src/main/sphinx/connector/faker.md b/docs/src/main/sphinx/connector/faker.md index 793064b7178e..2a3f01cd9c09 100644 --- a/docs/src/main/sphinx/connector/faker.md +++ b/docs/src/main/sphinx/connector/faker.md @@ -30,9 +30,9 @@ reading from tables returns random, but deterministic data. As a result, repeated invocation of a query returns identical data. See [](faker-usage) for more examples. -Schemas and tables in a catalog are not persisted, and are stored in the memory -of the coordinator only. They need to be recreated every time after restarting -the coordinator. +Schemas, tables, and views in a catalog are not persisted, and are stored in the +memory of the coordinator only. They need to be recreated every time after +restarting the coordinator. The following table details all general configuration properties: @@ -102,6 +102,18 @@ The following table details all supported column properties. sentence from the [Lorem](https://javadoc.io/doc/net.datafaker/datafaker/latest/net/datafaker/providers/base/Lorem.html) provider. +* - `min` + - Minimum generated value (inclusive). Cannot be set for character-based type + columns. +* - `max` + - Maximum generated value (inclusive). Cannot be set for character-based type + columns. +* - `allowed_values` + - List of allowed values. Cannot be set together with the `min`, or `max` + properties. +* - `step` + - If set, generate sequential values with this step. For date and time columns + set this to a duration. Cannot be set for character-based type columns. ::: ### Character types @@ -166,7 +178,7 @@ Faker supports the following non-character types: - `UUID` You can not use generator expressions for non-character-based columns. To limit -their data range, specify constraints in the `WHERE` clause - see +their data range, set the `min` and `max` column properties - see [](faker-usage). ### Unsupported types @@ -184,12 +196,11 @@ can be combined, like in the following example: ```sql CREATE TABLE faker.default.prices ( currency VARCHAR NOT NULL WITH (generator = '#{Currency.code}'), - price DECIMAL(8,2) NOT NULL + price DECIMAL(8,2) NOT NULL WITH (min = '0') ); SELECT JSON_OBJECT(KEY currency VALUE price) AS complex FROM faker.default.prices -WHERE price > 0 LIMIT 3; ``` @@ -238,6 +249,7 @@ To define the schema for generating data, it supports the following features: - [](/sql/drop-table) - [](/sql/create-schema) - [](/sql/drop-schema) +- [](sql-view-management) (faker-usage)= ## Usage @@ -260,40 +272,48 @@ CREATE TABLE generator.default.customer (LIKE production.public.customer EXCLUDI Insert random data into the original table, by selecting it from the `generator` catalog. Data generated by the Faker connector for columns of -non-character types cover the whole range of that data type. Add constraints to -adjust the data as desired. The following example ensures that date of birth -and age in years are related and realistic values. +non-character types cover the whole range of that data type. Set the `min` and +`max` column properties, to adjust the generated data as desired. The following +example ensures that date of birth and age in years are related and realistic +values. + +Start with getting the complete definition of +a table: + +```sql +SHOW CREATE TABLE production.public.customers; +``` + +Modify the output of the previous query and add some column properties. + +```sql +CREATE TABLE generator.default.customer ( + id UUID NOT NULL, + name VARCHAR NOT NULL, + address VARCHAR NOT NULL, + born_at DATE WITH (min = '1900-01-01', max = '2025-01-01'), + age_years INTEGER WITH (min = '0', max = '150'), + group_id INTEGER WITH (allowed_values = ARRAY['10', '32', '81']) +); +``` ```sql INSERT INTO production.public.customers SELECT * FROM generator.default.customers -WHERE - born_at BETWEEN CURRENT_DATE - INTERVAL '150' YEAR AND CURRENT_DATE - AND age_years BETWEEN 0 AND 150 LIMIT 100; ``` To generate even more realistic data, choose specific generators by setting the -`generator` property on columns. Start with getting the complete definition of -a table: - -```sql -SHOW CREATE TABLE production.public.customers; -``` - -Modify the output of the previous query and add some column properties. +`generator` property on columns. ```sql CREATE TABLE generator.default.customer ( id UUID NOT NULL, name VARCHAR NOT NULL WITH (generator = '#{Name.first_name} #{Name.last_name}'), address VARCHAR NOT NULL WITH (generator = '#{Address.fullAddress}'), - born_at DATE, - age_years INTEGER + born_at DATE WITH (min = '1900-01-01', max = '2025-01-01'), + age_years INTEGER WITH (min = '0', max = '150'), + group_id INTEGER WITH (allowed_values = ARRAY['10', '32', '81']) ); ``` - -## Limitations - -* It is not possible to choose the locale used by the Datafaker's generators. diff --git a/docs/src/main/sphinx/connector/hive.md b/docs/src/main/sphinx/connector/hive.md index cf58c3a000d0..b642004c06a0 100644 --- a/docs/src/main/sphinx/connector/hive.md +++ b/docs/src/main/sphinx/connector/hive.md @@ -403,7 +403,7 @@ configured object storage system and metadata stores: - {ref}`sql-view-management`; see also {ref}`Hive-specific view management ` -- [](sql-routine-management) +- [](udf-management) - {ref}`sql-security-operations`: see also {ref}`SQL standard-based authorization for object storage ` diff --git a/docs/src/main/sphinx/connector/iceberg.md b/docs/src/main/sphinx/connector/iceberg.md index 6ec6cd7bd4e0..42cb12927220 100644 --- a/docs/src/main/sphinx/connector/iceberg.md +++ b/docs/src/main/sphinx/connector/iceberg.md @@ -1502,6 +1502,18 @@ CREATE TABLE example.customers.orders ( WITH (sorted_by = ARRAY['order_date']) ``` +You can explicitly configure sort directions or null ordering in the following way: + +``` +CREATE TABLE example.customers.orders ( + order_id BIGINT, + order_date DATE, + account_number BIGINT, + customer VARCHAR, + country VARCHAR) +WITH (sorted_by = ARRAY['order_date DESC NULLS FIRST', 'order_id ASC NULLS LAST']) +``` + Sorting can be combined with partitioning on the same column. For example: ``` diff --git a/docs/src/main/sphinx/connector/ignite.md b/docs/src/main/sphinx/connector/ignite.md index abd43a529bd4..108ebc4a2a7e 100644 --- a/docs/src/main/sphinx/connector/ignite.md +++ b/docs/src/main/sphinx/connector/ignite.md @@ -75,9 +75,6 @@ configured connector to create a catalog named `sales`. ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - ## Table properties Table property usage example: @@ -173,20 +170,33 @@ Ignite. In addition to the {ref}`globally available ` and {ref}`read operation ` statements, the connector supports the following features: -- {doc}`/sql/insert` -- {doc}`/sql/update` -- {doc}`/sql/delete` -- {doc}`/sql/create-table` -- {doc}`/sql/create-table-as` -- {doc}`/sql/drop-table` -- {doc}`/sql/alter-table` +- [](/sql/insert), see also [](ignite-insert) +- [](/sql/update), see also [](ignite-update) +- [](/sql/delete) +- [](/sql/merge), see also [](ignite-merge) +- [](/sql/create-table) +- [](/sql/create-table-as) +- [](/sql/drop-table) +- [](/sql/alter-table), see also [](ignite-alter-table) +- [](ignite-procedures) + +(ignite-insert)= +```{include} non-transactional-insert.fragment +``` +(ignite-update)= ```{include} sql-update-limitation.fragment ``` +(ignite-merge)= +```{include} non-transactional-merge.fragment +``` + +(ignite-alter-table)= ```{include} alter-table-limitation.fragment ``` +(ignite-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment diff --git a/docs/src/main/sphinx/connector/mariadb.md b/docs/src/main/sphinx/connector/mariadb.md index e07d37cd585d..37d1762cce74 100644 --- a/docs/src/main/sphinx/connector/mariadb.md +++ b/docs/src/main/sphinx/connector/mariadb.md @@ -52,9 +52,6 @@ properties files. ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - (mariadb-fte-support)= ### Fault-tolerant execution support @@ -283,28 +280,37 @@ Complete list of [MariaDB data types](https://mariadb.com/kb/en/data-types/). (mariadb-sql-support)= ## SQL support -The connector provides read access and write access to data and metadata in -a MariaDB database. In addition to the {ref}`globally available -` and {ref}`read operation ` +The connector provides read access and write access to data and metadata in a +MariaDB database. In addition to the [globally +available](sql-globally-available) and [read operation](sql-read-operations) statements, the connector supports the following features: -- {doc}`/sql/insert` -- {doc}`/sql/update` -- {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` +- [](/sql/insert), see also [](mariadb-insert) +- [](/sql/update), see also [](mariadb-update) +- [](/sql/delete), see also [](mariadb-delete) +- [](/sql/truncate) +- [](/sql/create-table) +- [](/sql/create-table-as) +- [](/sql/drop-table) +- [](/sql/alter-table) +- [](/sql/create-schema) +- [](/sql/drop-schema) +- [](mariadb-procedures) +- [](mariadb-table-functions) + +(mariadb-insert)= +```{include} non-transactional-insert.fragment +``` +(mariadb-update)= ```{include} sql-update-limitation.fragment ``` +(mariadb-delete)= ```{include} sql-delete-limitation.fragment ``` +(mariadb-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -312,6 +318,7 @@ statements, the connector supports the following features: ```{include} procedures-execute.fragment ``` +(mariadb-table-functions)= ### Table functions The connector provides specific {doc}`table functions ` to diff --git a/docs/src/main/sphinx/connector/memory.md b/docs/src/main/sphinx/connector/memory.md index 2a7377a1dea7..23bc958abf80 100644 --- a/docs/src/main/sphinx/connector/memory.md +++ b/docs/src/main/sphinx/connector/memory.md @@ -69,7 +69,7 @@ statements, the connector supports the following features: - {doc}`/sql/alter-schema` - {doc}`/sql/comment` - [](sql-view-management) -- [](sql-routine-management) +- [](udf-management) ### TRUNCATE and DROP TABLE diff --git a/docs/src/main/sphinx/connector/mysql.md b/docs/src/main/sphinx/connector/mysql.md index 61d5457d786d..30f5e2bac925 100644 --- a/docs/src/main/sphinx/connector/mysql.md +++ b/docs/src/main/sphinx/connector/mysql.md @@ -99,9 +99,6 @@ creates a catalog named `sales` using the configured connector. ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - (mysql-fte-support)= ### Fault-tolerant execution support @@ -334,26 +331,35 @@ that catalog name instead of `example` in the above examples. ## SQL support The connector provides read access and write access to data and metadata in the -MySQL database. In addition to the {ref}`globally available ` and -{ref}`read operation ` statements, the connector supports -the following statements: - -- {doc}`/sql/insert` -- {doc}`/sql/update` -- {doc}`/sql/delete` -- {doc}`/sql/truncate` -- {doc}`/sql/create-table` -- {doc}`/sql/create-table-as` -- {doc}`/sql/drop-table` -- {doc}`/sql/create-schema` -- {doc}`/sql/drop-schema` +MySQL database. In addition to the [globally available](sql-globally-available) +and [read operation](sql-read-operations) statements, the connector supports the +following features: + +- [](/sql/insert), see also [](mysql-insert) +- [](/sql/update), see also [](mysql-update) +- [](/sql/delete), see also [](mysql-delete) +- [](/sql/truncate) +- [](/sql/create-table) +- [](/sql/create-table-as) +- [](/sql/drop-table) +- [](/sql/create-schema) +- [](/sql/drop-schema) +- [](mysql-procedures) +- [](mysql-table-functions) + +(mysql-insert)= +```{include} non-transactional-insert.fragment +``` +(mysql-update)= ```{include} sql-update-limitation.fragment ``` +(mysql-delete)= ```{include} sql-delete-limitation.fragment ``` +(mysql-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -361,6 +367,7 @@ the following statements: ```{include} procedures-execute.fragment ``` +(mysql-table-functions)= ### Table functions The connector provides specific {doc}`table functions ` to diff --git a/docs/src/main/sphinx/connector/non-transactional-merge.fragment b/docs/src/main/sphinx/connector/non-transactional-merge.fragment new file mode 100644 index 000000000000..60e905372a38 --- /dev/null +++ b/docs/src/main/sphinx/connector/non-transactional-merge.fragment @@ -0,0 +1,11 @@ +### Non-transactional MERGE + +The connector supports adding, updating, and deleting rows using [MERGE +statements](/sql/merge), if the `merge.non-transactional-merge.enabled` catalog +property or the corresponding `non_transactional_merge_enabled` catalog session +property is set to `true`. Merge is only supported for directly modifying target +tables. + +In rare cases, expections occur during the merge operation, potentially +resulting in a partial update. + diff --git a/docs/src/main/sphinx/connector/oracle.md b/docs/src/main/sphinx/connector/oracle.md index de4cb33b0b3d..238f046bb60a 100644 --- a/docs/src/main/sphinx/connector/oracle.md +++ b/docs/src/main/sphinx/connector/oracle.md @@ -98,9 +98,6 @@ you name the property file `sales.properties`, Trino creates a catalog named ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - (oracle-fte-support)= ### Fault-tolerant execution support @@ -405,29 +402,39 @@ fails. This is also true for the equivalent `VARCHAR` types. ## SQL support The connector provides read access and write access to data and metadata in -Oracle. In addition to the {ref}`globally available ` -and {ref}`read operation ` statements, the connector -supports the following statements: - -- {doc}`/sql/insert` -- {doc}`/sql/update` -- {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/comment` +Oracle. In addition to the [globally available](sql-globally-available) and +[read operation](sql-read-operations) statements, the connector supports the +following features: + +- [](/sql/insert), see also [](oracle-insert) +- [](/sql/update), see also [](oracle-update) +- [](/sql/delete), see also [](oracle-delete) +- [](/sql/truncate) +- [](/sql/create-table) +- [](/sql/create-table-as) +- [](/sql/drop-table) +- [](/sql/alter-table), see also [](oracle-alter-table) +- [](/sql/comment) +- [](oracle-procedures) +- [](oracle-table-functions) + +(oracle-insert)= +```{include} non-transactional-insert.fragment +``` +(oracle-update)= ```{include} sql-update-limitation.fragment ``` +(oracle-delete)= ```{include} sql-delete-limitation.fragment ``` +(oracle-alter-table)= ```{include} alter-table-limitation.fragment ``` +(oracle-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -435,6 +442,7 @@ supports the following statements: ```{include} procedures-execute.fragment ``` +(oracle-table-functions)= ### Table functions The connector provides specific {doc}`table functions ` to diff --git a/docs/src/main/sphinx/connector/phoenix.md b/docs/src/main/sphinx/connector/phoenix.md index a1116b13234f..7efdbe2e98c1 100644 --- a/docs/src/main/sphinx/connector/phoenix.md +++ b/docs/src/main/sphinx/connector/phoenix.md @@ -63,9 +63,6 @@ The following Phoenix-specific configuration properties are available: ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - ## Querying Phoenix tables The default empty schema in Phoenix maps to a schema named `default` in Trino. @@ -268,24 +265,35 @@ Use them in the same way as above: in the `WITH` clause of the `CREATE TABLE` st (phoenix-sql-support)= ## SQL support -The connector provides read and write access to data and metadata in -Phoenix. In addition to the {ref}`globally available -` and {ref}`read operation ` -statements, the connector supports the following features: - -- {doc}`/sql/insert` -- {doc}`/sql/update` -- {doc}`/sql/delete` -- {doc}`/sql/merge` -- {doc}`/sql/create-table` -- {doc}`/sql/create-table-as` -- {doc}`/sql/drop-table` -- {doc}`/sql/create-schema` -- {doc}`/sql/drop-schema` +The connector provides read and write access to data and metadata in Phoenix. In +addition to the [globally available](sql-globally-available) and [read +operation](sql-read-operations) statements, the connector supports the following +features: + +- [](/sql/insert), see also [](phoenix-insert) +- [](/sql/update) +- [](/sql/delete), see also [](phoenix-delete) +- [](/sql/merge), see also [](phoenix-merge) +- [](/sql/create-table) +- [](/sql/create-table-as) +- [](/sql/drop-table) +- [](/sql/create-schema) +- [](/sql/drop-schema) +- [](phoenix-procedures) + +(phoenix-insert)= +```{include} non-transactional-insert.fragment +``` +(phoenix-delete)= ```{include} sql-delete-limitation.fragment ``` +(phoenix-merge)= +```{include} non-transactional-merge.fragment +``` + +(phoenix-procedures)= ### Procedures ```{include} procedures-execute.fragment diff --git a/docs/src/main/sphinx/connector/postgresql.md b/docs/src/main/sphinx/connector/postgresql.md index 62d3d7789518..30eb90c0ce0d 100644 --- a/docs/src/main/sphinx/connector/postgresql.md +++ b/docs/src/main/sphinx/connector/postgresql.md @@ -112,21 +112,6 @@ catalog named `sales` using the configured connector. ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - -### Non-transactional MERGE - -The connector supports adding rows using {doc}`MERGE statements `. -However, the connector only support merge modifying directly to the target -table at current, to use merge you need to set the `merge.non-transactional-merge.enabled` -catalog property or the corresponding `non_transactional_merge_enabled` catalog session property to -`true`. - -Note that with this property enabled, data can be corrupted in rare cases where -exceptions occur during the merge operation. With transactions disabled, no -rollback can be performed. - (postgresql-fte-support)= ### Fault-tolerant execution support @@ -360,28 +345,46 @@ that catalog name instead of `example` in the above examples. ## SQL support The connector provides read access and write access to data and metadata in -PostgreSQL. In addition to the {ref}`globally available -` and {ref}`read operation ` -statements, the connector supports the following features: - -- {doc}`/sql/insert` -- {doc}`/sql/update` -- {doc}`/sql/delete` -- {doc}`/sql/truncate` -- {ref}`sql-schema-table-management` +PostgreSQL. In addition to the [globally available](sql-globally-available) and +[read operation](sql-read-operations) statements, the connector supports the +following features: + +- [](/sql/insert), see also [](postgresql-insert) +- [](/sql/update), see also [](postgresql-update) +- [](/sql/delete), see also [](postgresql-delete) +- [](/sql/merge), see also [](postgresql-merge) +- [](/sql/truncate) +- [](sql-schema-table-management), see also: + - [](postgresql-alter-table) + - [](postgresql-alter-schema) +- [](postgresql-procedures) +- [](postgresql-table-functions) + +(postgresql-insert)= +```{include} non-transactional-insert.fragment +``` +(postgresql-update)= ```{include} sql-update-limitation.fragment ``` +(postgresql-delete)= ```{include} sql-delete-limitation.fragment ``` +(postgresql-merge)= +```{include} non-transactional-merge.fragment +``` + +(postgresql-alter-table)= ```{include} alter-table-limitation.fragment ``` +(postgresql-alter-schema)= ```{include} alter-schema-limitation.fragment ``` +(postgresql-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -389,6 +392,7 @@ statements, the connector supports the following features: ```{include} procedures-execute.fragment ``` +(postgresql-table-functions)= ### Table functions The connector provides specific {doc}`table functions ` to diff --git a/docs/src/main/sphinx/connector/redshift.md b/docs/src/main/sphinx/connector/redshift.md index ba21b13134cd..1543834e5911 100644 --- a/docs/src/main/sphinx/connector/redshift.md +++ b/docs/src/main/sphinx/connector/redshift.md @@ -64,6 +64,43 @@ documentation](https://docs.aws.amazon.com/redshift/latest/mgmt/jdbc20-configura ```{include} jdbc-authentication.fragment ``` +### UNLOAD configuration + +This feature enables using Amazon S3 to efficiently transfer data out of Redshift +instead of the default single threaded JDBC based implementation. +The connector automatically triggers the appropriate `UNLOAD` command +on Redshift to extract the output from Redshift to the configured +S3 bucket in the form of Parquet files. These Parquet files are read in parallel +from S3 to improve latency of reading from Redshift tables. The Parquet +files will be removed when Trino finishes executing the query. It is recommended +to define a custom life cycle policy on the S3 bucket used for unloading the +Redshift query results. +This feature is supported only when the Redshift cluster and the configured S3 +bucket are in the same AWS region. + +The following table describes configuration properties for using +`UNLOAD` command in Redshift connector. `redshift.unload-location` must be set +to use `UNLOAD`. + +:::{list-table} UNLOAD configuration properties +:widths: 30, 60 +:header-rows: 1 + +* - Property value + - Description +* - `redshift.unload-location` + - A writeable location in Amazon S3, to be used for temporarily unloading + Redshift query results. +* - `redshift.unload-iam-role` + - Optional. Fully specified ARN of the IAM Role attached to the Redshift cluster. + Provided role will be used in `UNLOAD` command. IAM role must have access to + Redshift cluster and write access to S3 bucket. The default IAM role attached to + Redshift cluster is used when this property is not configured. +::: + +Additionally, define appropriate [S3 configurations](/object-storage/file-system-s3) +except `fs.native-s3.enabled`, required to read Parquet files from S3 bucket. + ### Multiple Redshift databases or clusters The Redshift connector can only access a single database within @@ -88,9 +125,6 @@ catalog named `sales` using the configured connector. ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - (redshift-fte-support)= ## Fault-tolerant execution support @@ -144,24 +178,37 @@ Redshift. In addition to the {ref}`globally available ` and {ref}`read operation ` statements, the connector supports the following features: -- {doc}`/sql/insert` -- {doc}`/sql/update` -- {doc}`/sql/delete` -- {doc}`/sql/truncate` -- {ref}`sql-schema-table-management` +- [](/sql/insert), see also [](redshift-insert) +- [](/sql/update), see also [](redshift-update) +- [](/sql/delete), see also [](redshift-delete) +- [](/sql/truncate) +- [](sql-schema-table-management), see also: + - [](redshift-alter-table) + - [](redshift-alter-schema) +- [](redshift-procedures) +- [](redshift-table-functions) + +(redshift-insert)= +```{include} non-transactional-insert.fragment +``` +(redshift-update)= ```{include} sql-update-limitation.fragment ``` +(redshift-delete)= ```{include} sql-delete-limitation.fragment ``` +(redshift-alter-table)= ```{include} alter-table-limitation.fragment ``` +(redshift-alter-schema)= ```{include} alter-schema-limitation.fragment ``` +(redshift-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -169,6 +216,7 @@ statements, the connector supports the following features: ```{include} procedures-execute.fragment ``` +(redshift-table-functions)= ### Table functions The connector provides specific {doc}`table functions ` to diff --git a/docs/src/main/sphinx/connector/singlestore.md b/docs/src/main/sphinx/connector/singlestore.md index e30f770e1798..c11467e1e330 100644 --- a/docs/src/main/sphinx/connector/singlestore.md +++ b/docs/src/main/sphinx/connector/singlestore.md @@ -85,9 +85,6 @@ will create a catalog named `sales` using the configured connector. ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - ## Querying SingleStore The SingleStore connector provides a schema for every SingleStore *database*. @@ -315,26 +312,35 @@ a SingleStore database. In addition to the {ref}`globally available ` and {ref}`read operation ` statements, the connector supports the following features: -- {doc}`/sql/insert` -- {doc}`/sql/update` -- {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` +- [](/sql/insert), see also [](singlestore-insert) +- [](/sql/update), see also [](singlestore-update) +- [](/sql/delete), see also [](singlestore-delete) +- [](/sql/truncate) +- [](/sql/create-table) +- [](/sql/create-table-as) +- [](/sql/alter-table), see also [](singlestore-alter-table) +- [](/sql/drop-table) +- [](/sql/create-schema) +- [](/sql/drop-schema) +- [](singlestore-procedures) + +(singlestore-insert)= +```{include} non-transactional-insert.fragment +``` +(singlestore-update)= ```{include} sql-update-limitation.fragment ``` +(singlestore-delete)= ```{include} sql-delete-limitation.fragment ``` +(singlestore-alter-table)= ```{include} alter-table-limitation.fragment ``` +(singlestore-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment diff --git a/docs/src/main/sphinx/connector/snowflake.md b/docs/src/main/sphinx/connector/snowflake.md index e17ca51a8b47..8d82faa722ce 100644 --- a/docs/src/main/sphinx/connector/snowflake.md +++ b/docs/src/main/sphinx/connector/snowflake.md @@ -62,9 +62,6 @@ multiple instances of the Snowflake connector. ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - % snowflake-type-mapping: ## Type mapping @@ -233,21 +230,28 @@ No other types are supported. (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 ` +The connector provides read access and write access to data and metadata in a +Snowflake database. In addition to the [globally +available](sql-globally-available) and [read operation](sql-read-operations) 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` +- [](/sql/insert), see also [](snowflake-insert) +- [](/sql/delete) +- [](/sql/truncate) +- [](/sql/create-table) +- [](/sql/create-table-as) +- [](/sql/drop-table) +- [](/sql/alter-table) +- [](/sql/create-schema) +- [](/sql/drop-schema) +- [](snowflake-procedures) +- [](snowflake-table-functions) + +(snowflake-insert)= +```{include} non-transactional-insert.fragment +``` +(snowflake-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -255,6 +259,7 @@ statements, the connector supports the following features: ```{include} procedures-execute.fragment ``` +(snowflake-table-functions)= ### Table functions The connector provides specific [table functions](/functions/table) to diff --git a/docs/src/main/sphinx/connector/sql-delete-limitation.fragment b/docs/src/main/sphinx/connector/sql-delete-limitation.fragment index 5c3a201ca369..aeb1e9bd9e18 100644 --- a/docs/src/main/sphinx/connector/sql-delete-limitation.fragment +++ b/docs/src/main/sphinx/connector/sql-delete-limitation.fragment @@ -1,4 +1,4 @@ -### SQL DELETE +### DELETE limitation If a ``WHERE`` clause is specified, the ``DELETE`` operation only works if the predicate in the clause can be fully pushed down to the data source. diff --git a/docs/src/main/sphinx/connector/sql-update-limitation.fragment b/docs/src/main/sphinx/connector/sql-update-limitation.fragment index 6143a8e0762d..0e3ece216322 100644 --- a/docs/src/main/sphinx/connector/sql-update-limitation.fragment +++ b/docs/src/main/sphinx/connector/sql-update-limitation.fragment @@ -1,4 +1,4 @@ -### UPDATE +### UPDATE limitation Only `UPDATE` statements with constant assignments and predicates are supported. For example, the following statement is supported because the values diff --git a/docs/src/main/sphinx/connector/sqlserver.md b/docs/src/main/sphinx/connector/sqlserver.md index 705083eb3281..469864d9fe9e 100644 --- a/docs/src/main/sphinx/connector/sqlserver.md +++ b/docs/src/main/sphinx/connector/sqlserver.md @@ -111,9 +111,6 @@ behavior of the connector and the issues queries to the database. ```{include} jdbc-case-insensitive-matching.fragment ``` -```{include} non-transactional-insert.fragment -``` - (sqlserver-fte-support)= ### Fault-tolerant execution support @@ -325,25 +322,37 @@ For Trino `VARCHAR(n)`: ## SQL support The connector provides read access and write access to data and metadata in SQL -Server. In addition to the {ref}`globally available ` -and {ref}`read operation ` statements, the connector -supports the following features: +Server. In addition to the [globally available](sql-globally-available) and +[read operation](sql-read-operations) statements, the connector supports the +following features: + +- [](/sql/insert), see also [](sqlserver-insert) +- [](/sql/update), see also [](sqlserver-update) +- [](/sql/delete), see also [](sqlserver-delete) +- [](/sql/truncate) +- [](sql-schema-table-management), see also: + - [](sqlserver-alter-table) +- [](sqlserver-procedures) +- [](sqlserver-table-functions) -- {doc}`/sql/insert` -- {doc}`/sql/update` -- {doc}`/sql/delete` -- {doc}`/sql/truncate` -- {ref}`sql-schema-table-management` +(sqlserver-insert)= +```{include} non-transactional-insert.fragment +``` + +(sqlserver-update)= ```{include} sql-update-limitation.fragment ``` +(sqlserver-delete)= ```{include} sql-delete-limitation.fragment ``` +(sqlserver-alter-table)= ```{include} alter-table-limitation.fragment ``` +(sqlserver-procedures)= ### Procedures ```{include} jdbc-procedures-flush.fragment @@ -351,6 +360,7 @@ supports the following features: ```{include} procedures-execute.fragment ``` +(sqlserver-table-functions)= ### Table functions The connector provides specific {doc}`table functions ` to diff --git a/docs/src/main/sphinx/connector/vertica.md b/docs/src/main/sphinx/connector/vertica.md index 72960dba32fb..79f2ff079b44 100644 --- a/docs/src/main/sphinx/connector/vertica.md +++ b/docs/src/main/sphinx/connector/vertica.md @@ -165,13 +165,16 @@ features: - [](/sql/create-table) - [](/sql/create-table-as) - [](/sql/drop-table) -- [](/sql/alter-table) excluding `DROP COLUMN` +- [](/sql/alter-table) excluding `DROP COLUMN`, see also [](vertica-alter-table) - [](/sql/create-schema) - [](/sql/drop-schema) +- [](vertica-table-functions) +(vertica-alter-table)= ```{include} alter-table-limitation.fragment ``` +(vertica-table-functions)= ## Table functions The connector provides specific [table functions](/functions/table) to diff --git a/docs/src/main/sphinx/functions.md b/docs/src/main/sphinx/functions.md index c445861ecce0..cb931d755667 100644 --- a/docs/src/main/sphinx/functions.md +++ b/docs/src/main/sphinx/functions.md @@ -12,7 +12,7 @@ Refer to the following sections for further details: 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). +[](/udf). ## Functions by name diff --git a/docs/src/main/sphinx/functions/conditional.md b/docs/src/main/sphinx/functions/conditional.md index d4086848294a..6aad6280f331 100644 --- a/docs/src/main/sphinx/functions/conditional.md +++ b/docs/src/main/sphinx/functions/conditional.md @@ -51,7 +51,7 @@ SELECT a, b, END ``` -SQL routines can use [`CASE` statements](/routines/case) that use a slightly +SQL UDFs can use [`CASE` statements](/udf/sql/case) that use a slightly different syntax from the CASE expressions. Specifically note the requirements for terminating each clause with a semicolon `;` and the usage of `END CASE`. @@ -95,9 +95,9 @@ SELECT FROM tpch.sf1.orders; ``` -SQL routines can use [`IF` statements](/routines/if) that use a slightly -different syntax from `IF` expressions. Specifically note the requirement -for terminating each clause with a semicolon `;` and the usage of `END IF`. +SQL UDFs can use [`IF` statements](/udf/sql/if) that use a slightly different +syntax from `IF` expressions. Specifically note the requirement for terminating +each clause with a semicolon `;` and the usage of `END IF`. (coalesce-function)= ## COALESCE diff --git a/docs/src/main/sphinx/index.md b/docs/src/main/sphinx/index.md index 16e8ab72011b..24ea617a83bb 100644 --- a/docs/src/main/sphinx/index.md +++ b/docs/src/main/sphinx/index.md @@ -12,9 +12,9 @@ optimizer connector object-storage functions +udf language sql -routines develop glossary appendix diff --git a/docs/src/main/sphinx/language/sql-support.md b/docs/src/main/sphinx/language/sql-support.md index 767db2af93cf..8dc3b693d51d 100644 --- a/docs/src/main/sphinx/language/sql-support.md +++ b/docs/src/main/sphinx/language/sql-support.md @@ -118,10 +118,10 @@ connector to connector: - {doc}`/sql/drop-materialized-view` - {doc}`/sql/refresh-materialized-view` -(sql-routine-management)= -### Routine management +(udf-management)= +### User-defined function management -The following statements are used to manage [catalog routines](routine-catalog): +The following statements are used to manage [](udf-catalog): - [](/sql/create-function) - [](/sql/drop-function) diff --git a/docs/src/main/sphinx/object-storage/file-system-s3.md b/docs/src/main/sphinx/object-storage/file-system-s3.md index 926d31cb870d..e18f188cdcc8 100644 --- a/docs/src/main/sphinx/object-storage/file-system-s3.md +++ b/docs/src/main/sphinx/object-storage/file-system-s3.md @@ -174,6 +174,9 @@ The security mapping must provide one or more configuration settings: - `kmsKeyId`: ID of KMS-managed key to be used for client-side encryption. - `allowedKmsKeyIds`: KMS-managed key IDs that are allowed to be specified as an extra credential. If list cotains `*`, then any key can be specified via extra credential. +- `sseCustomerKey`: The customer provided key (SSE-C) for server-side encryption. +- `allowedSseCustomerKey`: The SSE-C keys that are allowed to be specified as an extra + credential. If list cotains `*`, then any key can be specified via extra credential. - `endpoint`: The S3 storage endpoint server. This optional property can be used to override S3 endpoints on a per-bucket basis. - `region`: The S3 region to connect to. This optional property can be used @@ -262,6 +265,8 @@ Example JSON configuration: - The name of the *extra credential* used to provide the IAM role. * - `s3.security-mapping.kms-key-id-credential-name` - The name of the *extra credential* used to provide the KMS-managed key ID. +* - `s3.security-mapping.sse-customer-key-credential-name` + - The name of the *extra credential* used to provide the server-side encryption with customer-provided keys (SSE-C). * - `s3.security-mapping.refresh-period` - How often to refresh the security mapping configuration, specified as a {ref}`prop-type-duration`. By default, the configuration is not refreshed. diff --git a/docs/src/main/sphinx/redirects.txt b/docs/src/main/sphinx/redirects.txt index 361a63fc6989..26e0254da98a 100644 --- a/docs/src/main/sphinx/redirects.txt +++ b/docs/src/main/sphinx/redirects.txt @@ -10,3 +10,18 @@ connector/atop.md connector/removed.md connector/localfile.md connector/removed.md connector/accumulo.md connector/removed.md security/apache-ranger-access-control.md security/ranger-access-control.md +routines.md udf.md +routines/function.md udf/function.md +routines/introduction.md udf/introduction.md +routines/begin.md udf/sql/begin.md +routines/case.md udf/sql/case.md +routines/declare.md udf/sql/declare.md +routines/examples.md udf/sql/examples.md +routines/if.md udf/sql/if.md +routines/iterate.md udf/sql/iterate.md +routines/leave.md udf/sql/leave.md +routines/loop.md udf/sql/loop.md +routines/repeat.md udf/sql/repeat.md +routines/return.md udf/sql/return.md +routines/set.md udf/sql/set.md +routines/while.md udf/sql/while.md diff --git a/docs/src/main/sphinx/release.md b/docs/src/main/sphinx/release.md index 2d083b797a47..c2bac1c08263 100644 --- a/docs/src/main/sphinx/release.md +++ b/docs/src/main/sphinx/release.md @@ -6,6 +6,7 @@ ```{toctree} :maxdepth: 1 +release/release-468 release/release-467 release/release-466 release/release-465 diff --git a/docs/src/main/sphinx/release/release-431.md b/docs/src/main/sphinx/release/release-431.md index 5a80e2d172fb..068cc79657ba 100644 --- a/docs/src/main/sphinx/release/release-431.md +++ b/docs/src/main/sphinx/release/release-431.md @@ -2,7 +2,7 @@ ## General -* Add support for [](/routines). ({issue}`19308`) +* Add support for [](/udf/sql). ({issue}`19308`) * Add support for [](/sql/create-function) and [](/sql/drop-function) statements. ({issue}`19308`) * Add support for the `REPLACE` modifier to the `CREATE TABLE` statement. ({issue}`13180`) * Disallow a `null` offset for the {func}`lead` and {func}`lag` functions. ({issue}`19003`) @@ -27,7 +27,7 @@ ## Hive connector -* Add support for [SQL routine management](sql-routine-management). ({issue}`19308`) +* Add support for [](udf-management). ({issue}`19308`) * Replace the `hive.metastore-timeout` Hive metastore configuration property with the `hive.metastore.thrift.client.connect-timeout` and `hive.metastore.thrift.client.read-timeout` properties. ({issue}`19390`) @@ -50,7 +50,7 @@ ## Memory connector -* Add support for [SQL routine management](sql-routine-management). ({issue}`19308`) +* Add support for [](udf-management). ({issue}`19308`) ## SPI diff --git a/docs/src/main/sphinx/release/release-436.md b/docs/src/main/sphinx/release/release-436.md index b1a0bab0e7c9..c6abe7357eaf 100644 --- a/docs/src/main/sphinx/release/release-436.md +++ b/docs/src/main/sphinx/release/release-436.md @@ -6,7 +6,7 @@ [](jvm-config). ({issue}`20010`) * Improve performance by not generating redundant predicates. ({issue}`16520`) * Fix query failure when invoking the `json_table` function. ({issue}`20122`) -* Fix query hang when a [SQL routine](/routines) dereferences a row field. ({issue}`19997`). +* Fix query hang when a [](/udf/sql) dereferences a row field. ({issue}`19997`). * Fix potential incorrect results when using the {func}`ST_Centroid` and {func}`ST_Buffer` functions for tiny geometries. ({issue}`20237`) diff --git a/docs/src/main/sphinx/release/release-440.md b/docs/src/main/sphinx/release/release-440.md index 2a79828a8e5f..5490782be738 100644 --- a/docs/src/main/sphinx/release/release-440.md +++ b/docs/src/main/sphinx/release/release-440.md @@ -12,7 +12,7 @@ * Fix query failure when a check constraint is null. ({issue}`20906`) * Fix query failure for aggregations over `CASE` expressions when the input evaluation could throw an error. ({issue}`20652`) -* Fix incorrect behavior of the else clause in a SQL routines with a single +* Fix incorrect behavior of the else clause in a SQL UDFs with a single if/end condition. ({issue}`20926`) * Fix the `ALTER TABLE EXECUTE optimize` queries failing due to exceeding the open writer limit. ({issue}`20871`) diff --git a/docs/src/main/sphinx/release/release-446.md b/docs/src/main/sphinx/release/release-446.md index eadcbe190fea..05f6b0bf0f16 100644 --- a/docs/src/main/sphinx/release/release-446.md +++ b/docs/src/main/sphinx/release/release-446.md @@ -10,9 +10,9 @@ statement. ({issue}`21619`) * Fix `CREATE CATALOG` statements including quotes in catalog names. ({issue}`21399`) * Fix potential query failure when a column name ends with a `:`. ({issue}`21676`) -* Fix potential query failure when a [SQL routine](/routines) contains a label +* Fix potential query failure when a [](/udf/sql) contains a label reference in a `LEAVE`, `ITERATE`, `REPEAT`, or `WHILE` statement. ({issue}`21682`) -* Fix query failure when [SQL routines](/routines) use the `NULLIF` or `BETWEEN` +* Fix query failure when [](/udf/sql) use the `NULLIF` or `BETWEEN` functions. ({issue}`19820`) * Fix potential query failure due to worker nodes running out of memory in concurrent scenarios. ({issue}`21706`) diff --git a/docs/src/main/sphinx/release/release-447.md b/docs/src/main/sphinx/release/release-447.md index d9e675f7e6b7..b1aee4fd4d31 100644 --- a/docs/src/main/sphinx/release/release-447.md +++ b/docs/src/main/sphinx/release/release-447.md @@ -13,7 +13,7 @@ ## CLI -* Fix incorrect error location markers for SQL routines causing CLI to print +* Fix incorrect error location markers for SQL UDFs causing the CLI to print exceptions. ({issue}`21357`) ## Delta Lake connector diff --git a/docs/src/main/sphinx/release/release-453.md b/docs/src/main/sphinx/release/release-453.md index ecf70c8eea80..e5c6e63cdf1d 100644 --- a/docs/src/main/sphinx/release/release-453.md +++ b/docs/src/main/sphinx/release/release-453.md @@ -70,7 +70,7 @@ `hive.metastore.glue.use-web-identity-token-credentials-provider` configuration property. ({issue}`15267`) * Fix failure to read Hive tables migrated to Iceberg with Apache Spark. ({issue}`11338`) -* Fix failure for `CREATE FUNCTION` with SQL routine storage in Glue when +* Fix failure for `CREATE FUNCTION` with SQL UDF storage in Glue when `hive.metastore.glue.catalogid` is set. ({issue}`22717`) ## Hudi connector diff --git a/docs/src/main/sphinx/release/release-454.md b/docs/src/main/sphinx/release/release-454.md index 7c66071bd7ec..6aa22f305814 100644 --- a/docs/src/main/sphinx/release/release-454.md +++ b/docs/src/main/sphinx/release/release-454.md @@ -23,7 +23,7 @@ ## Web UI -* Add information about which tables and routines have been referenced by a +* Add information about which tables and UDFs have been referenced by a query. ({issue}`20843`) ## JDBC driver diff --git a/docs/src/main/sphinx/release/release-468.md b/docs/src/main/sphinx/release/release-468.md new file mode 100644 index 000000000000..601d41728fba --- /dev/null +++ b/docs/src/main/sphinx/release/release-468.md @@ -0,0 +1,51 @@ +# Release 468 (17 Dec 2024) + +## General + +* Add experimental support for [](/udf/python). ({issue}`24378`) +* Add cluster overview to the [](/admin/preview-web-interface). ({issue}`23600`) +* Add new node states `DRAINING` and `DRAINED` to make it possible to reactivate + a draining worker node. ({issue}`24444 `) + +## BigQuery connector + +* Improve performance when reading external + [BigLake](https://cloud.google.com/bigquery/docs/biglake-intro) tables. ({issue}`21016`) + +## Delta Lake connector + +* {{breaking}} Reduce coordinator memory usage for the Delta table metadata + cache and enable configuration `delta.metadata.cache-max-retained-size` to + control memory usage. Remove the configuration property + `delta.metadata.cache-size` and increase the default for + `delta.metadata.cache-ttl` to `30m`. ({issue}`24432`) + +## Hive connector + +* Enable mismatched bucket execution optimization by default. This can be + disabled with `hive.optimize-mismatched-bucket-count` configuration property + and the `optimize_mismatched_bucket_count` session property. ({issue}`23432`) +* Improve performance by deactivating bucket execution when not useful in query + processing. ({issue}`23432`) + +## Iceberg connector + +* Improve performance when running a join or aggregation on a bucketed table + with bucketed execution. This can be deactivated with the + `iceberg.bucket-execution` configuration property and the + `bucket_execution_enabled` session property. ({issue}`23432`) +* Deprecate the `iceberg.materialized-views.storage-schema` configuration + property. ({issue}`24398`) +* {{breaking}} Rename the `partitions` column in the `$manifests` metadata table + to `partition_summaries`. ({issue}`24103`) +* Avoid excessive resource usage on coordinator when reading Iceberg system + tables. ({issue}`24396`) + +## PostgreSQL connector + +* Add support for non-transactional [MERGE statements](/sql/merge). ({issue}`23034`) + +## SPI + +* Add partitioning push down, which a connector can use to activate optional + partitioning or choose between multiple partitioning strategies. ({issue}`23432`) diff --git a/docs/src/main/sphinx/routines.md b/docs/src/main/sphinx/routines.md deleted file mode 100644 index 541608d529d2..000000000000 --- a/docs/src/main/sphinx/routines.md +++ /dev/null @@ -1,25 +0,0 @@ -# SQL routines - -A SQL routine is a custom, user-defined function authored by a user of Trino in -a client and written in the SQL routine language. Routines are scalar functions -that return a single output value. More details are available in the following -sections: - -```{toctree} -:maxdepth: 1 - -Introduction -Examples -routines/begin -routines/case -routines/declare -routines/function -routines/if -routines/iterate -routines/leave -routines/loop -routines/repeat -routines/return -routines/set -routines/while -``` diff --git a/docs/src/main/sphinx/routines/begin.md b/docs/src/main/sphinx/routines/begin.md deleted file mode 100644 index c1ef143fd70a..000000000000 --- a/docs/src/main/sphinx/routines/begin.md +++ /dev/null @@ -1,57 +0,0 @@ -# BEGIN - -## Synopsis - -```text -BEGIN - [ DECLARE ... ] - statements -END -``` - -## Description - -Marks the start and end of a block in a [SQL routine](/routines/introduction). -`BEGIN` can be used wherever a statement can be used to group multiple -statements together and to declare variables local to the block. A typical use -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 -the main body of the routine, separated by `;`. The following statements can be -used: - -* [](/routines/case) -* [](/routines/if) -* [](/routines/iterate) -* [](/routines/leave) -* [](/routines/loop) -* [](/routines/repeat) -* [](/routines/return) -* [](/routines/set) -* [](/routines/while) -* Nested [](/routines/begin) blocks - -## Examples - -The following example computes the value `42`: - -```sql -FUNCTION meaning_of_life() - RETURNS tinyint - BEGIN - DECLARE a tinyint DEFAULT 6; - DECLARE b tinyint DEFAULT 7; - RETURN a * b; - END -``` - -Further examples of varying complexity that cover usage of the `BEGIN` statement -in combination with other statements are available in the [SQL routines examples -documentation](/routines/examples). - -## See also - -* [](/routines/introduction) -* [](/routines/function) diff --git a/docs/src/main/sphinx/routines/function.md b/docs/src/main/sphinx/routines/function.md deleted file mode 100644 index 21df3a95c392..000000000000 --- a/docs/src/main/sphinx/routines/function.md +++ /dev/null @@ -1,102 +0,0 @@ -# FUNCTION - -## Synopsis - -```text -FUNCTION name ( [ parameter_name data_type [, ...] ] ) - RETURNS type - [ LANGUAGE language] - [ NOT? DETERMINISTIC ] - [ RETURNS NULL ON NULL INPUT ] - [ CALLED ON NULL INPUT ] - [ SECURITY { DEFINER | INVOKER } ] - [ COMMENT description] - statements -``` - -## Description - -Declare a SQL routine. - -The `name` of the routine. [Inline routines](routine-inline) can use a simple -string. [Catalog routines](routine-catalog) must qualify the name of the catalog -and schema, delimited by `.`, to store the routine or rely on the [default -catalog and schema for routine storage](/admin/properties-sql-environment). - -The list of parameters is a comma-separated list of names `parameter_name` and -data types `data_type`, see [data type](/language/types). An empty list, specified as -`()` is also valid. - -The `type` value after the `RETURNS` keyword identifies the [data -type](/language/types) of the routine output. - -The optional `LANGUAGE` characteristic identifies the language used for the -routine definition with `language`. Only `SQL` is supported. - -The optional `DETERMINISTIC` or `NOT DETERMINISTIC` characteristic declares that -the routine is deterministic. This means that repeated routine calls with -identical input parameters yield the same result. For SQL language routines, a -routine is non-deterministic if it calls any non-deterministic routines and -[functions](/functions). By default, routines are assume to have a deterministic -behavior. - -The optional `RETURNS NULL ON NULL INPUT` characteristic declares that the -routine returns a `NULL` value when any of the input parameters are `NULL`. -The routine is not invoked with a `NULL` input value. - -The `CALLED ON NULL INPUT` characteristic declares that the routine is invoked -with `NULL` input parameter values. - -The `RETURNS NULL ON NULL INPUT` and `CALLED ON NULL INPUT` characteristics are -mutually exclusive, with `CALLED ON NULL INPUT` as the default. - -The security declaration of `SECURITY INVOKER` or `SECURITY DEFINER` is only -valid for catalog routines. It sets the mode for processing the routine with the -permissions of the user who calls the routine (`INVOKER`) or the user who -created the routine (`DEFINER`). - -The `COMMENT` characteristic can be used to provide information about the -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. Routines must -contain a `RETURN` statement at the end of the top-level block, even if it's -unreachable. - -## Examples - -A simple catalog function: - -```sql -CREATE FUNCTION example.default.meaning_of_life() - RETURNS BIGINT - RETURN 42; -``` - -And used: - -```sql -SELECT example.default.meaning_of_life(); -- returns 42 -``` - -Equivalent usage with an inline function: - -```sql -WITH FUNCTION meaning_of_life() - RETURNS BIGINT - RETURN 42 -SELECT meaning_of_life(); -``` - -Further examples of varying complexity that cover usage of the `FUNCTION` -statement in combination with other statements are available in the [SQL -routines examples documentation](/routines/examples). - -## See also - -* [](/routines/introduction) -* [](/routines/begin) -* [](/routines/return) -* [](/sql/create-function) - diff --git a/docs/src/main/sphinx/routines/introduction.md b/docs/src/main/sphinx/routines/introduction.md deleted file mode 100644 index b6fb55d05631..000000000000 --- a/docs/src/main/sphinx/routines/introduction.md +++ /dev/null @@ -1,192 +0,0 @@ -# Introduction to SQL routines - -A SQL routine is a custom, user-defined function authored by a user of Trino and -written in the SQL routine language. Routines are scalar functions that return a -single output value, similar to [built-in functions](/functions). - -[Declare the routine](routine-declaration) with a `FUNCTION` definition using -the supported SQL routine statements. A routine can be declared and used as an -[inline routine](routine-inline) or declared as a [catalog -routine](routine-catalog) and used repeatedly. - -(routine-inline)= -## Inline routines - -An inline routine declares and uses the routine within a query processing -context. The routine is declared in a `WITH` block before the query: - -```sql -WITH - FUNCTION abc(x integer) - RETURNS integer - RETURN x * 2 -SELECT abc(21); -``` - -Inline routine names must follow SQL identifier naming conventions, and cannot -contain `.` characters. - -The routine declaration is only valid within the context of the query. A -separate later invocation of the routine is not possible. If this is desired, -use a [catalog routine](routine-catalog). - -Multiple inline routine declarations are comma-separated, and can include -routines calling each other, as long as a called routine is declared before -the first invocation. - -```sql -WITH - FUNCTION abc(x integer) - RETURNS integer - RETURN x * 2, - FUNCTION xyz(x integer) - RETURNS integer - RETURN abc(x) + 1 -SELECT xyz(21); -``` - -Note that inline routines can mask and override the meaning of a built-in function: - -```sql -WITH - FUNCTION abs(x integer) - RETURNS integer - RETURN x * 2 -SELECT abs(-10); -- -20, not 10! -``` - -(routine-catalog)= -## Catalog routines - -You can store a routine in the context of a catalog, if the connector used in -the catalog supports routine storage. The following connectors support catalog -routine storage: - -* [](/connector/hive) -* [](/connector/memory) - -In this scenario, the following commands can be used: - -* [](/sql/create-function) to create and store a routine. -* [](/sql/drop-function) to remove a routine. -* [](/sql/show-functions) to display a list of routines in a catalog. - -Catalog routines must use a name that combines the catalog name and schema name -with the routine name, such as `example.default.power` for the `power` routine -in the `default` schema of the `example` catalog. - -Invocation must use the fully qualified name, such as `example.default.power`. - -(routine-sql-environment)= -## SQL environment configuration - -Configuration of the `sql.default-function-catalog` and -`sql.default-function-schema` [](/admin/properties-sql-environment) allows you -to set the default storage for SQL routines. The catalog and schema must be -added to the `sql.path` as well. This enables users to call SQL routines and -perform all [SQL routine management](sql-routine-management) without specifying -the full path to the routine. - -:::{note} -Use the [](/connector/memory) in a catalog for simple storing and -testing of your SQL routines. -::: - -(routine-declaration)= -## Routine declaration - -Refer to the documentation for the [](/routines/function) keyword for more -details about declaring the routine overall. The routine body is composed with -statements from the following list: - -* [](/routines/begin) -* [](/routines/case) -* [](/routines/declare) -* [](/routines/if) -* [](/routines/iterate) -* [](/routines/leave) -* [](/routines/loop) -* [](/routines/repeat) -* [](/routines/return) -* [](/routines/set) -* [](/routines/while) - -Statements can also use [built-in functions and operators](/functions) as well -as other routines, although recursion is not supported for routines. - -Find simple examples in each statement documentation, and refer to the [example -documentation](/routines/examples) for more complex use cases that combine -multiple statements. - -:::{note} -User-defined functions can alternatively be written in Java and deployed as a -plugin. Details are available in the [developer guide](/develop/functions). -::: - -(routine-label)= -## Labels - -Routines can contain labels as markers for a specific block in the declaration -before the following keywords: - -* `CASE` -* `IF` -* `LOOP` -* `REPEAT` -* `WHILE` - -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 - SET a = a + 1; - IF a <= 3 THEN - ITERATE top; - END IF; - SET b = b + 1; - UNTIL a >= 10 -END REPEAT; -``` - -Labels can be used with the `ITERATE` and `LEAVE` statements to continue -processing the block or leave the block. This flow control is also supported for -nested blocks and labels. - -## Recommendations - -Processing routines can potentially be resource intensive on the cluster in -terms of memory and processing. Take the following considerations into account -when writing and running SQL routines: - -* Some checks for the runtime behavior of routines are in place. For example, - routines that take longer to process than a hardcoded threshold are - automatically terminated. -* Avoid creation of arrays in a looping construct. Each iteration creates a - separate new array with all items and copies the data for each modification, - leaving the prior array in memory for automated clean up later. Use a [lambda - expression](/functions/lambda) instead of the loop. -* Avoid concatenating strings in a looping construct. Each iteration creates a - separate new string and copying the old string for each modification, leaving - the prior string in memory for automated clean up later. Use a [lambda - expression](/functions/lambda) instead of the loop. -* Most routines should declare the `RETURNS NULL ON NULL INPUT` characteristics - unless the code has some special handling for null values. You must declare - this explicitly since `CALLED ON NULL INPUT` is the default characteristic. - -## Limitations - -The following limitations apply to SQL routines. - -* 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. diff --git a/docs/src/main/sphinx/routines/return.md b/docs/src/main/sphinx/routines/return.md deleted file mode 100644 index cc8bfe483989..000000000000 --- a/docs/src/main/sphinx/routines/return.md +++ /dev/null @@ -1,36 +0,0 @@ -# RETURN - -## Synopsis - -```text -RETURN expression -``` - -## Description - -Provide the value from a [SQL routines](/routines/introduction) to the caller. -The value is the result of evaluating the expression. It can be a static value, -a declared variable or a more complex expression. - -## Examples - -The following examples return a static value, the result of an expression, and -the value of the variable x: - -```sql -RETURN 42; -RETURN 6 * 7; -RETURN x; -``` - -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) 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 55644dfc12b5..7651f438a33a 100644 --- a/docs/src/main/sphinx/security/file-system-access-control.md +++ b/docs/src/main/sphinx/security/file-system-access-control.md @@ -142,11 +142,11 @@ Permissions required for executing functions: * - `CREATE FUNCTION` - `all` - `ownership` - - Not all connectors support [catalog routines](routine-catalog). + - Not all connectors support [](udf-catalog). * - `DROP FUNCTION` - `all` - `ownership` - - Not all connectors support [catalog routines](routine-catalog). + - Not all connectors support [](udf-catalog). ::: (system-file-auth-visibility)= diff --git a/docs/src/main/sphinx/sql/create-function.md b/docs/src/main/sphinx/sql/create-function.md index 513b64557506..b5ace2dde8a0 100644 --- a/docs/src/main/sphinx/sql/create-function.md +++ b/docs/src/main/sphinx/sql/create-function.md @@ -4,24 +4,23 @@ ```text CREATE [OR REPLACE] FUNCTION - routine_definition + udf_definition ``` ## Description -Create or replace a [](routine-catalog). The `routine_definition` is composed of -the usage of [](/routines/function) and nested statements. The name of the -routine must be fully qualified with catalog and schema location, unless the -[default SQL routine storage catalog and -schema](/admin/properties-sql-environment) are configured. The connector used in -the catalog must support routine storage. +Create or replace a [](udf-catalog). The `udf_definition` is composed of the +usage of [](/udf/function) and nested statements. The name of the UDF must be +fully qualified with catalog and schema location, unless the [default UDF +storage catalog and schema](/admin/properties-sql-environment) are configured. +The connector used in the catalog must support UDF storage. -The optional `OR REPLACE` clause causes the routine to be replaced if it already +The optional `OR REPLACE` clause causes the UDF to be replaced if it already exists rather than raising an error. ## Examples -The following example creates the `meaning_of_life` routine in the `default` +The following example creates the `meaning_of_life` UDF in the `default` schema of the `example` catalog: ```sql @@ -32,7 +31,7 @@ CREATE FUNCTION example.default.meaning_of_life() END; ``` -If the [default catalog and schema for routine +If the [default catalog and schema for UDF storage](/admin/properties-sql-environment) is configured, you can use the following more compact syntax: @@ -42,12 +41,12 @@ CREATE FUNCTION meaning_of_life() RETURNS bigint RETURN 42; Further examples of varying complexity that cover usage of the `FUNCTION` statement in combination with other statements are available in the [SQL -routines examples documentation](/routines/examples). +UDF examples documentation](/udf/sql/examples). ## See also * [](/sql/drop-function) * [](/sql/show-create-function) * [](/sql/show-functions) -* [](/routines/introduction) +* [](/udf) * [](/admin/properties-sql-environment) diff --git a/docs/src/main/sphinx/sql/drop-function.md b/docs/src/main/sphinx/sql/drop-function.md index 76235b0f6de9..0183facb80f1 100644 --- a/docs/src/main/sphinx/sql/drop-function.md +++ b/docs/src/main/sphinx/sql/drop-function.md @@ -3,38 +3,37 @@ ## Synopsis ```text -DROP FUNCTION [ IF EXISTS ] routine_name ( [ [ parameter_name ] data_type [, ...] ] ) +DROP FUNCTION [ IF EXISTS ] udf_name ( [ [ parameter_name ] data_type [, ...] ] ) ``` ## Description -Removes a [](routine-catalog). The value of `routine_name` -must be fully qualified with catalog and schema location of the routine, unless -the [default SQL routine storage catalog and -schema](/admin/properties-sql-environment) are configured. +Removes a [catalog UDF](udf-catalog). The value of `udf_name` must be fully +qualified with catalog and schema location of the UDF, unless the [default UDF storage catalog and schema](/admin/properties-sql-environment) are +configured. -The `data_type`s must be included for routines that use parameters to ensure the -routine with the correct name and parameter signature is removed. +The `data_type`s must be included for UDFs that use parameters to ensure the UDF +with the correct name and parameter signature is removed. The optional `IF EXISTS` clause causes the error to be suppressed if the function does not exist. ## Examples -The following example removes the `meaning_of_life` routine in the `default` -schema of the `example` catalog: +The following example removes the `meaning_of_life` UDF in the `default` schema +of the `example` catalog: ```sql DROP FUNCTION example.default.meaning_of_life(); ``` -If the routine uses a input parameter, the type must be added: +If the UDF uses a input parameter, the type must be added: ```sql DROP FUNCTION multiply_by_two(bigint); ``` -If the [default catalog and schema for routine +If the [default catalog and schema for UDF storage](/admin/properties-sql-environment) is configured, you can use the following more compact syntax: @@ -47,5 +46,5 @@ DROP FUNCTION meaning_of_life(); * [](/sql/create-function) * [](/sql/show-create-function) * [](/sql/show-functions) -* [](/routines/introduction) +* [](/udf) * [](/admin/properties-sql-environment) diff --git a/docs/src/main/sphinx/sql/select.md b/docs/src/main/sphinx/sql/select.md index 5d6b93b6ed23..82938f816871 100644 --- a/docs/src/main/sphinx/sql/select.md +++ b/docs/src/main/sphinx/sql/select.md @@ -3,7 +3,7 @@ ## Synopsis ```text -[ WITH FUNCTION sql_routines ] +[ WITH FUNCTION udf ] [ WITH [ RECURSIVE ] with_query [, ...] ] SELECT [ ALL | DISTINCT ] select_expression [, ...] [ FROM from_item [, ...] ] @@ -70,10 +70,10 @@ Retrieve rows from zero or more tables. ## WITH FUNCTION clause -The `WITH FUNCTION` clause allows you to define a list of inline SQL routines -that are available for use in the rest of the query. +The `WITH FUNCTION` clause allows you to define a list of [](udf-inline) that +are available for use in the rest of the query. -The following example declares and uses two inline routines: +The following example declares and uses two inline UDFs: ```sql WITH @@ -87,8 +87,8 @@ SELECT hello('Finn') || ' and ' || bye('Joe'); -- Hello Finn! and Bye Joe! ``` -Find further information about routines in general, inline routines, all -supported statements, and examples in [](/routines). +Find further information about UDFs in general, inline UDFs, all supported +statements, and examples in [](/udf). ## WITH clause diff --git a/docs/src/main/sphinx/sql/show-create-function.md b/docs/src/main/sphinx/sql/show-create-function.md index 1015726b6de1..bac15c00b448 100644 --- a/docs/src/main/sphinx/sql/show-create-function.md +++ b/docs/src/main/sphinx/sql/show-create-function.md @@ -23,5 +23,5 @@ SHOW CREATE FUNCTION example.default.meaning_of_life; * [](/sql/create-function) * [](/sql/drop-function) * [](/sql/show-functions) -* [](/routines/introduction) +* [](/udf) * [](/admin/properties-sql-environment) diff --git a/docs/src/main/sphinx/sql/show-functions.md b/docs/src/main/sphinx/sql/show-functions.md index 9d9fe0067591..41136258bda7 100644 --- a/docs/src/main/sphinx/sql/show-functions.md +++ b/docs/src/main/sphinx/sql/show-functions.md @@ -10,7 +10,7 @@ SHOW FUNCTIONS [ FROM schema ] [ LIKE pattern ] List functions in `schema` or all functions in the current session path. This can include built-in functions, [functions from a custom -plugin](/develop/functions), and [SQL routines](/routines). +plugin](/develop/functions), and [](/udf). For each function returned, the following information is displayed: @@ -30,8 +30,8 @@ filter the results to the desired subset. ## Examples -List all SQL routines and plugin functions in the `default` schema of the -`example` catalog: +List all UDFs and plugin functions in the `default` schema of the `example` +catalog: ```sql SHOW FUNCTIONS FROM example.default; @@ -62,7 +62,7 @@ Example output: ## See also * [](/functions) -* [](/routines) +* [](/udf) * [](/develop/functions) * [](/sql/create-function) * [](/sql/drop-function) diff --git a/docs/src/main/sphinx/udf.md b/docs/src/main/sphinx/udf.md new file mode 100644 index 000000000000..4e8c54a61420 --- /dev/null +++ b/docs/src/main/sphinx/udf.md @@ -0,0 +1,17 @@ +# User-defined functions + +A user-defined function (UDF) is a custom function authored by a user of Trino +in a client application. UDFs are scalar functions that return a single output +value, similar to [built-in functions](/functions). + +More details are available in the following sections: + +```{toctree} +:titlesonly: true +:maxdepth: 1 + +udf/introduction +udf/function +udf/sql +udf/python +``` diff --git a/docs/src/main/sphinx/udf/function.md b/docs/src/main/sphinx/udf/function.md new file mode 100644 index 000000000000..f5564ab574af --- /dev/null +++ b/docs/src/main/sphinx/udf/function.md @@ -0,0 +1,113 @@ +# FUNCTION + +## Synopsis + +```text +FUNCTION name ( [ parameter_name data_type [, ...] ] ) + RETURNS type + [ LANGUAGE language] + [ NOT? DETERMINISTIC ] + [ RETURNS NULL ON NULL INPUT ] + [ CALLED ON NULL INPUT ] + [ SECURITY { DEFINER | INVOKER } ] + [ COMMENT description] + [ WITH ( property_name = expression [, ...] ) ] + { statements | AS definition } +``` + +## Description + +Declare a [user-defined function](/udf). + +The `name` of the UDF. [](udf-inline) can use a simple string. [](udf-catalog) +must qualify the name of the catalog and schema, delimited by `.`, to store the +UDF or rely on the [default catalog and schema for UDF +storage](/admin/properties-sql-environment). + +The list of parameters is a comma-separated list of names `parameter_name` and +data types `data_type`, see [data type](/language/types). An empty list, specified as +`()` is also valid. + +The `type` value after the `RETURNS` keyword identifies the [data +type](/language/types) of the UDF output. + +The optional `LANGUAGE` characteristic identifies the language used for the UDF +definition with `language`. The `SQL` and `PYTHON` languages are supported by +default. Additional languages may be supported via a language engine plugin. +If not specified, the default language is `SQL`. + +The optional `DETERMINISTIC` or `NOT DETERMINISTIC` characteristic declares that +the UDF is deterministic. This means that repeated UDF calls with identical +input parameters yield the same result. A UDF is non-deterministic if it calls +any non-deterministic UDFs and [functions](/functions). By default, UDFs are +assumed to have a deterministic behavior. + +The optional `RETURNS NULL ON NULL INPUT` characteristic declares that the UDF +returns a `NULL` value when any of the input parameters are `NULL`. The UDF is +not invoked with a `NULL` input value. + +The `CALLED ON NULL INPUT` characteristic declares that the UDF is invoked with +`NULL` input parameter values. + +The `RETURNS NULL ON NULL INPUT` and `CALLED ON NULL INPUT` characteristics are +mutually exclusive, with `CALLED ON NULL INPUT` as the default. + +The security declaration of `SECURITY INVOKER` or `SECURITY DEFINER` is only +valid for catalog UDFs. It sets the mode for processing the UDF with the +permissions of the user who calls the UDF (`INVOKER`) or the user who created +the UDF (`DEFINER`). + +The `COMMENT` characteristic can be used to provide information about the +function to other users as `description`. The information is accessible with +[](/sql/show-functions). + +The optional `WITH` clause can be used to specify properties for the function. +The available properties vary based on the function language. For +[](/udf/python), the `handler` property specifies the name of the Python +function to invoke. + +For SQL UDFs the body of the UDF can either be a simple single `RETURN` +statement with an expression, or compound list of `statements` in a `BEGIN` +block. UDF must contain a `RETURN` statement at the end of the top-level block, +even if it's unreachable. + +For UDFs in other languages, the `definition` is enclosed in a `$$`-quoted +string. + +## Examples + +A simple catalog function: + +```sql +CREATE FUNCTION example.default.meaning_of_life() + RETURNS BIGINT + RETURN 42; +``` + +And used: + +```sql +SELECT example.default.meaning_of_life(); -- returns 42 +``` + +Equivalent usage with an inline function: + +```sql +WITH FUNCTION meaning_of_life() + RETURNS BIGINT + RETURN 42 +SELECT meaning_of_life(); +``` + +Further examples of varying complexity that cover usage of the `FUNCTION` +statement in combination with other statements are available in the [SQL UDF +documentation](/udf/sql/examples) and the [Python UDF +documentation](/udf/python). + +## See also + +* [](/udf) +* [](/udf/sql) +* [](/udf/python) +* [](/sql/create-function) + diff --git a/docs/src/main/sphinx/udf/introduction.md b/docs/src/main/sphinx/udf/introduction.md new file mode 100644 index 000000000000..da479dc491f1 --- /dev/null +++ b/docs/src/main/sphinx/udf/introduction.md @@ -0,0 +1,127 @@ +# Introduction to UDFs + +A user-defined function (UDF) is a custom function authored by a user of Trino +in a client application. UDFs are scalar functions that return a single output +value, similar to [built-in functions](/functions). + +:::{note} +Custom functions can alternatively be written in Java and deployed as a +plugin. Details are available in the [developer guide](/develop/functions). +::: + +(udf-declaration)= +## UDF declaration + +Declare the UDF with the SQL [](/udf/function) keyword and the supported +statements for [](/udf/sql) or [](/udf/python). + +A UDF can be declared as an [inline UDF](udf-inline) to be used in the current +query, or declared as a [catalog UDF](udf-catalog) to be used in any future +query. + +(udf-inline)= +## Inline user-defined functions + +An inline user-defined function (inline UDF) declares and uses the UDF within a +query processing context. The UDF is declared in a `WITH` block before the +query: + +```sql +WITH + FUNCTION doubleup(x integer) + RETURNS integer + RETURN x * 2 +SELECT doubleup(21); +-- 42 +``` + +Inline UDF names must follow SQL identifier naming conventions, and cannot +contain `.` characters. + +The UDF declaration is only valid within the context of the query. A separate +later invocation of the UDF is not possible. If this is desired, use a [catalog +UDF](udf-catalog). + +Multiple inline UDF declarations are comma-separated, and can include UDFs +calling each other, as long as a called UDF is declared before the first +invocation. + +```sql +WITH + FUNCTION doubleup(x integer) + RETURNS integer + RETURN x * 2, + FUNCTION doubleupplusone(x integer) + RETURNS integer + RETURN doubleup(x) + 1 +SELECT doubleupplusone(21); +-- 43 +``` + +Note that inline UDFs can mask and override the meaning of a built-in function: + +```sql +WITH + FUNCTION abs(x integer) + RETURNS integer + RETURN x * 2 +SELECT abs(-10); -- -20, not 10! +``` + +(udf-catalog)= +## Catalog user-defined functions + +You can store a UDF in the context of a catalog, if the connector used in the +catalog supports UDF storage. The following connectors support catalog UDF +storage: + +* [](/connector/hive) +* [](/connector/memory) + +In this scenario, the following commands can be used: + +* [](/sql/create-function) to create and store a UDF. +* [](/sql/drop-function) to remove a UDF. +* [](/sql/show-functions) to display a list of UDFs in a catalog. + +Catalog UDFs must use a name that combines the catalog name and schema name with +the UDF name, such as `example.default.power` for the `power` UDF in the +`default` schema of the `example` catalog. + +Invocation must use the fully qualified name, such as `example.default.power`. + +(udf-sql-environment)= +## SQL environment configuration for UDFs + +Configuration of the `sql.default-function-catalog` and +`sql.default-function-schema` [](/admin/properties-sql-environment) allows you +to set the default storage for UDFs. The catalog and schema must be added to the +`sql.path` as well. This enables users to call UDFs and perform all +[](udf-management) without specifying the full path to the UDF. + +:::{note} +Use the [](/connector/memory) in a catalog for simple storing and +testing of your UDFs. +::: + +## Recommendations + +Processing UDFs can potentially be resource intensive on the cluster in +terms of memory and processing. Take the following considerations into account +when writing and running UDFs: + +* Some checks for the runtime behavior of queries, and therefore UDF processing, + are in place. For example, if a query takes longer to process than a hardcoded + threshold, processing is automatically terminated. +* Avoid creation of arrays in a looping construct. Each iteration creates a + separate new array with all items and copies the data for each modification, + leaving the prior array in memory for automated clean up later. Use a [lambda + expression](/functions/lambda) instead of the loop. +* Avoid concatenating strings in a looping construct. Each iteration creates a + separate new string and copying the old string for each modification, leaving + the prior string in memory for automated clean up later. Use a [lambda + expression](/functions/lambda) instead of the loop. +* Most UDFs should declare the `RETURNS NULL ON NULL INPUT` characteristics + unless the code has some special handling for null values. You must declare + this explicitly since `CALLED ON NULL INPUT` is the default characteristic. + diff --git a/docs/src/main/sphinx/udf/python.md b/docs/src/main/sphinx/udf/python.md new file mode 100644 index 000000000000..56900550c137 --- /dev/null +++ b/docs/src/main/sphinx/udf/python.md @@ -0,0 +1,182 @@ +# Python user-defined functions + +A Python user-defined function is a [user-defined function](/udf) that uses the +[Python programming language and statements](python-udf-lang) for the definition +of the function. + +:::{warning} +Python user-defined functions are an experimental feature. +::: + +## Python UDF declaration + +Declare a Python UDF as [inline](udf-inline) or [catalog UDF](udf-catalog) with +the following steps: + +* Use the [](/udf/function) keyword to declare the UDF name and parameters. +* Add the `RETURNS` declaration to specify the data type of the result. +* Set the `LANGUAGE` to `PYTHON`. +* Declare the name of the Python function to call with the `handler` property in + the `WITH` block. +* Use `$$` to enclose the Python code after the `AS` keyword. +* Add the function from the handler property and ensure it returns the declared + data type. +* Expand your Python code section to implement the function using the available + [Python language](python-udf-lang). + +The following snippet shows pseudo-code: + +```text +FUNCTION python_udf_name(input_parameter data_type) + RETURNS result_data_type + LANGUAGE PYTHON + WITH (handler = 'python_function') + AS $$ + ... + def python_function(input): + return ... + ... + $$ +``` + +A minimal example declares the UDF `doubleup` that returns the input integer +value `x` multiplied by two. The example shows declaration as [](udf-inline) and +invocation with the value `21` to yield the result `42`. + +Set the language to `PYTHON` to override the default `SQL` for [](/udf/sql). +The Python code is enclosed with ``$$` and must use valid formatting. + +```text +WITH + FUNCTION doubleup(x integer) + RETURNS integer + LANGUAGE PYTHON + WITH (handler = 'twice') + AS $$ + def twice(a): + return a * 2 + $$ +SELECT doubleup(21); +-- 42 +``` + +The same UDF can also be declared as [](udf-catalog). + +Refer to the [](/udf/python/examples) for more complex use cases and examples. + +```{toctree} +:titlesonly: true +:hidden: + +/udf/python/examples +``` + +(python-udf-lang)= +## Python language details + +The Trino Python UDF integrations uses Python 3.13.0 in a sandboxed environment. +Python code runs within a WebAssembly (WASM) runtime within the Java virtual +machine running Trino. + +Python language rules including indents must be observed. + +Python UDFs therefore only have access to the Python language and core libraries +included in the sandboxed runtime. Access to external resources with network or +file system operations is not supported. Usage of other Python libraries as well +as command line tools or package managers is not supported. + +The following libraries are explicitly removed from the runtime and therefore +not available within a Python UDF: + +* `bdb` +* `concurrent` +* `curses` +* `ensurepip` +* `doctest` +* `idlelib` +* `multiprocessing` +* `pdb` +* `pydoc` +* `socketserver` +* `sqlite3` +* `ssl` +* `subprocess` +* `tkinter` +* `turtle` +* `unittest` +* `venv` +* `webbrowser` +* `wsgiref` +* `xmlrpc` + +## Type mapping + +The following table shows supported Trino types and their corresponding Python +types for input and output values of a Python UDF: + +:::{list-table} +:widths: 40, 60 +:header-rows: 1 + +* - Trino type + - Python type +* - `ROW` + - `tuple` +* - `ARRAY` + - `list` +* - `MAP` + - `dict` +* - `BOOLEAN` + - `bool` +* - `TINYINT` + - `int` +* - `SMALLINT` + - `int` +* - `INTEGER` + - `int` +* - `BIGINT` + - `int` +* - `REAL` + - `float` +* - `DOUBLE` + - `float` +* - `DECIMAL` + - `decimal.Decimal` +* - `VARCHAR` + - `str` +* - `VARBINARY` + - `bytes` +* - `DATE` + - `datetime.date` +* - `TIME` + - `datetime.time` +* - `TIME WITH TIME ZONE` + - `datetime.time` with `datetime.tzinfo` +* - `TIMESTAMP` + - `datetime.datetime` +* - `TIMESTAMP WITH TIME ZONE` + - `datetime.datetime` with `datetime.tzinfo` +* - `INTERVAL YEAR TO MONTH` + - `int` as the number of months +* - `INTERVAL DAY TO SECOND` + - `datetime.timedelta` +* - `JSON` + - `str` +* - `UUID` + - `uuid.UUID` +* - `IPADDRESS` + - `ipaddress.IPv4Address` or `ipaddress.IPv6Address` + +::: + +### Time and timestamp + +Python `datetime` and `time` objects only support microsecond precision. +Trino argument values with greater precision are rounded when converted to +Python values, and Python return values are rounded if the Trino return type +has less than microsecond precision. + +### Timestamp with time zone + +Only fixed offset time zones are supported. Timestamps with political time zones +have the zone converted to the zone's offset for the timestamp's instant. diff --git a/docs/src/main/sphinx/udf/python/examples.md b/docs/src/main/sphinx/udf/python/examples.md new file mode 100644 index 000000000000..19b28729b86c --- /dev/null +++ b/docs/src/main/sphinx/udf/python/examples.md @@ -0,0 +1,157 @@ +# Example Python UDFs + +After learning about [](/udf/python), the following sections show examples +of valid Python UDFs. + +The UDFs are suitable as [](udf-inline) or [](udf-catalog), +after adjusting the name and the example invocations. + +## Inline and catalog Python UDFs + +The following section shows the differences in usage with inline and catalog +UDFs with a simple Python UDF example. The same pattern applies to all other +following sections. + +A very simple Python UDF that returns the static int value `42` without +requiring any input: + +```text +FUNCTION answer() +LANGUAGE PYTHON +RETURNS int +WITH (handler='theanswer') +AS $$ +def theanswer(): + return 42 +$$ +``` + +A full example of this UDF as inline UDF and usage in a string concatenation +with a cast: + +```text +WITH + FUNCTION answer() + RETURNS int + LANGUAGE PYTHON + WITH (handler='theanswer') + AS $$ + def theanswer(): + return 42 + $$ +SELECT 'The answer is ' || CAST(answer() as varchar); +-- The answer is 42 +``` + +Provided the catalog `example` supports UDF storage in the `default` schema, you +can use the following: + +```text +CREATE FUNCTION example.default.answer() + RETURNS int + LANGUAGE PYTHON + WITH (handler='theanswer') + AS $$ + def theanswer(): + return 42 + $$; +``` + +With the UDF stored in the catalog, you can run the UDF multiple times without +repeated definition: + +```sql +SELECT example.default.answer() + 1; -- 43 +SELECT 'The answer is ' || CAST(example.default.answer() as varchar); -- The answer is 42 +``` + +Alternatively, you can configure the SQL PATH in the [](config-properties) to a +catalog and schema that support UDF storage: + +```properties +sql.default-function-catalog=example +sql.default-function-schema=default +sql.path=example.default +``` + +Now you can manage UDFs without the full path: + +```text +CREATE FUNCTION answer() + RETURNS int + LANGUAGE PYTHON + WITH (handler='theanswer') + AS $$ + def theanswer(): + return 42 + $$; +``` + +UDF invocation works without the full path: + +```sql +SELECT answer() + 5; -- 47 +``` + +## XOR + +The following example implements a `xor` function for a logical Exclusive OR +operation on two boolean input parameters and tests it with two invocations: + +```text +WITH FUNCTION xor(a boolean, b boolean) +RETURNS boolean +LANGUAGE PYTHON +WITH (handler = 'bool_xor') +AS $$ +import operator +def bool_xor(a, b): + return operator.xor(a, b) +$$ +SELECT xor(true, false), xor(false, true); +``` + +Result of the query: + +``` + true | true +``` + +## reverse_words + +The following example uses a more elaborate Python script to reverse the +characters in each word of the input string `s` of type `varchar` and tests the +function. + +```text +WITH FUNCTION reverse_words(s varchar) +RETURNS varchar +LANGUAGE PYTHON +WITH (handler = 'reverse_words') +AS $$ +import re + +def reverse(s): + str = "" + for i in s: + str = i + str + return str + +pattern = re.compile(r"\w+[.,'!?\"]\w*") + +def process_word(word): + # Reverse only words without non-letter signs + return word if pattern.match(word) else reverse(word) + +def reverse_words(payload): + text_words = payload.split(' ') + return ' '.join([process_word(w) for w in text_words]) +$$ +SELECT reverse_words('Civic, level, dna racecar era semordnilap'); +``` + +Result of the query: + +``` +Civic, level, and racecar are palindromes +``` \ No newline at end of file diff --git a/docs/src/main/sphinx/udf/sql.md b/docs/src/main/sphinx/udf/sql.md new file mode 100644 index 000000000000..df70207075b4 --- /dev/null +++ b/docs/src/main/sphinx/udf/sql.md @@ -0,0 +1,107 @@ +# SQL user-defined functions + +A SQL user-defined function, also known as SQL routine, is a [user-defined +function](/udf) that uses the SQL routine language and statements for the +definition of the function. + +## SQL UDF declaration + +Declare a SQL UDF using the [](/udf/function) keyword and the following +statements can be used in addition to [built-in functions and +operators](/functions) and other UDFs: + +* [](/udf/sql/begin) +* [](/udf/sql/case) +* [](/udf/sql/declare) +* [](/udf/sql/if) +* [](/udf/sql/iterate) +* [](/udf/sql/leave) +* [](/udf/sql/loop) +* [](/udf/sql/repeat) +* [](/udf/sql/return) +* [](/udf/sql/set) +* [](/udf/sql/while) + +```{toctree} +:titlesonly: true +:hidden: + +sql/examples +sql/begin +sql/case +sql/declare +sql/if +sql/iterate +sql/leave +sql/loop +sql/repeat +sql/return +sql/set +sql/while +``` + +A minimal example declares the UDF `doubleup` that returns the input integer +value `x` multiplied by two. The example shows declaration as [](udf-inline) and +invocation with the value 21 to yield the result 42: + +```sql +WITH + FUNCTION doubleup(x integer) + RETURNS integer + RETURN x * 2 +SELECT doubleup(21); +-- 42 +``` + +The same UDF can also be declared as [](udf-catalog). + +Find simple examples in each statement documentation, and refer to the +[](/udf/sql/examples) for more complex use cases that combine multiple +statements. + +(udf-sql-label)= +## Labels + +SQL UDFs can contain labels as markers for a specific block in the declaration +before the following keywords: + +* `CASE` +* `IF` +* `LOOP` +* `REPEAT` +* `WHILE` + +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 + SET a = a + 1; + IF a <= 3 THEN + ITERATE top; + END IF; + SET b = b + 1; + UNTIL a >= 10 +END REPEAT; +``` + +Labels can be used with the `ITERATE` and `LEAVE` statements to continue +processing the block or leave the block. This flow control is also supported for +nested blocks and labels. + +## Limitations + +The following limitations apply to SQL UDFs. + +* UDFs 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 UDF. + +Specifically this means that UDFs can not use `SELECT` queries to retrieve +data or any other queries to process data within the UDF. Instead queries can +use UDFs to process data. UDFs only work on data provided as input values and +only provide output data from the `RETURN` statement. \ No newline at end of file diff --git a/docs/src/main/sphinx/udf/sql/begin.md b/docs/src/main/sphinx/udf/sql/begin.md new file mode 100644 index 000000000000..7e850383193e --- /dev/null +++ b/docs/src/main/sphinx/udf/sql/begin.md @@ -0,0 +1,56 @@ +# BEGIN + +## Synopsis + +```text +BEGIN + [ DECLARE ... ] + statements +END +``` + +## Description + +Marks the start and end of a block in a [](/udf/sql). `BEGIN` can be used +wherever a statement can be used to group multiple statements together and to +declare variables local to the block. A typical use case is as first statement +within a [](/udf/function). Blocks can also be nested. + +After the `BEGIN` keyword, you can add variable declarations using +[](/udf/sql/declare) statements, followed by one or more statements that define +the main body of the SQL UDF, separated by `;`. The following statements can be +used: + +* [](/udf/sql/case) +* [](/udf/sql/if) +* [](/udf/sql/iterate) +* [](/udf/sql/leave) +* [](/udf/sql/loop) +* [](/udf/sql/repeat) +* [](/udf/sql/return) +* [](/udf/sql/set) +* [](/udf/sql/while) +* Nested [](/udf/sql/begin) blocks + +## Examples + +The following example computes the value `42`: + +```sql +FUNCTION meaning_of_life() + RETURNS tinyint + BEGIN + DECLARE a tinyint DEFAULT 6; + DECLARE b tinyint DEFAULT 7; + RETURN a * b; + END +``` + +Further examples of varying complexity that cover usage of the `BEGIN` statement +in combination with other statements are available in the [](/udf/sql/examples). + +## See also + +* [](/udf) +* [](/udf/sql) +* [](/udf/function) diff --git a/docs/src/main/sphinx/routines/case.md b/docs/src/main/sphinx/udf/sql/case.md similarity index 86% rename from docs/src/main/sphinx/routines/case.md rename to docs/src/main/sphinx/udf/sql/case.md index f7264d08b096..146699360299 100644 --- a/docs/src/main/sphinx/routines/case.md +++ b/docs/src/main/sphinx/udf/sql/case.md @@ -25,7 +25,7 @@ END ## Description The `CASE` statement is an optional construct to allow conditional processing -in [SQL routines](/routines/introduction). +in [](/udf/sql). The `WHEN` clauses are evaluated sequentially, stopping after the first match, and therefore the order of the statements is significant. The statements of the @@ -54,10 +54,9 @@ FUNCTION simple_case(a bigint) ``` Further examples of varying complexity that cover usage of the `CASE` statement -in combination with other statements are available in the [SQL routines examples -documentation](/routines/examples). +in combination with other statements are available in the [](/udf/sql/examples). ## See also -* [](/routines/introduction) +* [](/udf/sql) * [Conditional expressions using `CASE`](case-expression) diff --git a/docs/src/main/sphinx/routines/declare.md b/docs/src/main/sphinx/udf/sql/declare.md similarity index 80% rename from docs/src/main/sphinx/routines/declare.md rename to docs/src/main/sphinx/udf/sql/declare.md index 2238ca2de0c0..518b6b4552dd 100644 --- a/docs/src/main/sphinx/routines/declare.md +++ b/docs/src/main/sphinx/udf/sql/declare.md @@ -8,8 +8,8 @@ DECLARE identifier [, ...] type [ DEFAULT expression ] ## Description -Use the `DECLARE` statement directly after the [](/routines/begin) keyword in -[](/routines) to define one or more variables with an `identifier` as name. Each +Use the `DECLARE` statement directly after the [](/udf/sql/begin) keyword in +[](/udf/sql) to define one or more variables with an `identifier` as name. Each statement must specify the [data type](/language/types) of the variable with `type`. It can optionally include a default, initial value defined by an `expression`. The default value is `NULL` if not specified. @@ -43,10 +43,10 @@ DECLARE start_time timestamp(3) with time zone DEFAULT now(); ``` Further examples of varying complexity that cover usage of the `DECLARE` -statement in combination with other statements are available in the [SQL -routines examples documentation](/routines/examples). +statement in combination with other statements are available in the +[](/udf/sql/examples). ## See also -* [](/routines/introduction) +* [](/udf/sql) * [](/language/types) diff --git a/docs/src/main/sphinx/routines/examples.md b/docs/src/main/sphinx/udf/sql/examples.md similarity index 85% rename from docs/src/main/sphinx/routines/examples.md rename to docs/src/main/sphinx/udf/sql/examples.md index 40667856e53f..2ffd63a45fab 100644 --- a/docs/src/main/sphinx/routines/examples.md +++ b/docs/src/main/sphinx/udf/sql/examples.md @@ -1,24 +1,27 @@ -# Example SQL routines +# Example SQL UDFs - -After learning about [SQL routines from the -introduction](/routines/introduction), the following sections show numerous -examples of valid SQL routines. The routines are suitable as [inline -routines](routine-inline) or [catalog routines](routine-catalog), after -adjusting the name and the example invocations. +After learning about [](/udf/sql), the following sections show numerous examples +of valid SQL UDFs. The UDFs are suitable as [](udf-inline) or [](udf-catalog), +after adjusting the name and the example invocations. The examples combine numerous supported statements. Refer to the specific statement documentation for further details: -* [](/routines/function) for general SQL routine declaration -* [](/routines/begin) and [](/routines/declare) for routine blocks -* [](/routines/set) for assigning values to variables -* [](/routines/return) for returning routine results -* [](/routines/case) and [](/routines/if) for conditional flows -* [](/routines/loop), [](/routines/repeat), and [](/routines/while) for looping constructs -* [](/routines/iterate) and [](/routines/leave) for flow control +* [](/udf/function) for general UDF declaration +* [](/udf/sql/begin) and [](/udf/sql/declare) for SQL UDF blocks +* [](/udf/sql/set) for assigning values to variables +* [](/udf/sql/return) for returning results +* [](/udf/sql/case) and [](/udf/sql/if) for conditional flows +* [](/udf/sql/loop), [](/udf/sql/repeat), and [](/udf/sql/while) for looping constructs +* [](/udf/sql/iterate) and [](/udf/sql/leave) for flow control + +## Inline and catalog UDFs -A very simple routine that returns a static value without requiring any input: +The following section shows the differences in usage with inline and catalog +UDFs with a simple SQL UDF example. The same pattern applies to all other +following sections. + +A very simple SQL UDF that returns a static value without requiring any input: ```sql FUNCTION answer() @@ -26,10 +29,8 @@ RETURNS BIGINT RETURN 42 ``` -## Inline and catalog routines - -A full example of this routine as inline routine and usage in a string -concatenation with a cast: +A full example of this UDF as inline UDF and usage in a string concatenation +with a cast: ```sql WITH @@ -40,33 +41,33 @@ SELECT 'The answer is ' || CAST(answer() as varchar); -- The answer is 42 ``` -Provided the catalog `example` supports routine storage in the `default` schema, -you can use the following: +Provided the catalog `example` supports UDF storage in the `default` schema, you +can use the following: ```sql -USE example.default; CREATE FUNCTION example.default.answer() RETURNS BIGINT RETURN 42; ``` -With the routine stored in the catalog, you can run the routine multiple times -without repeated definition: +With the UDF stored in the catalog, you can run the UDF multiple times without +repeated definition: ```sql SELECT example.default.answer() + 1; -- 43 -SELECT 'The answer is' || CAST(example.default.answer() as varchar); -- The answer is 42 +SELECT 'The answer is ' || CAST(example.default.answer() as varchar); -- The answer is 42 ``` -Alternatively, you can configure the SQL environment in the -[](config-properties) to a catalog and schema that support SQL routine storage: +Alternatively, you can configure the SQL PATH in the [](config-properties) to a +catalog and schema that support UDF storage: ```properties sql.default-function-catalog=example sql.default-function-schema=default +sql.path=example.default ``` -Now you can manage SQL routines without the full path: +Now you can manage UDFs without the full path: ```sql CREATE FUNCTION answer() @@ -74,7 +75,7 @@ CREATE FUNCTION answer() RETURN 42; ``` -SQL routine invocation works without the full path: +UDF invocation works without the full path: ```sql SELECT answer() + 5; -- 47 @@ -82,7 +83,7 @@ SELECT answer() + 5; -- 47 ## Declaration examples -The result of calling the routine `answer()` is always identical, so you can +The result of calling the UDF `answer()` is always identical, so you can declare it as deterministic, and add some other information: ```sql @@ -94,10 +95,10 @@ COMMENT 'Provide the answer to the question about life, the universe, and everyt RETURN 42 ``` -The comment and other information about the routine is visible in the output of +The comment and other information about the UDF is visible in the output of [](/sql/show-functions). -A simple routine that returns a greeting back to the input string `fullname` +A simple UDF that returns a greeting back to the input string `fullname` concatenating two strings and the input value: ```sql @@ -112,7 +113,7 @@ Following is an example invocation: SELECT hello('Jane Doe'); -- Hello, Jane Doe! ``` -A first example routine, that uses multiple statements in a `BEGIN` block. It +A first example UDF, that uses multiple statements in a `BEGIN` block. It calculates the result of a multiplication of the input integer with `99`. The `bigint` data type is used for all variables and values. The value of integer `99` is cast to `bigint` in the default value assignment for the variable `x`: @@ -134,7 +135,7 @@ SELECT times_ninety_nine(CAST(2 as bigint)); -- 198 ## Conditional flows -A first example of conditional flow control in a routine using the `CASE` +A first example of conditional flow control in a SQL UDF using the `CASE` statement. The simple `bigint` input value is compared to a number of values: ```sql @@ -165,7 +166,7 @@ SELECT simple_case(100); -- other (from else clause) SELECT simple_case(null); -- null .. but really?? ``` -A second example of a routine with a `CASE` statement, this time with two +A second example of a SQL UDF with a `CASE` statement, this time with two parameters, showcasing the importance of the order of the conditions: ```sql @@ -200,15 +201,15 @@ 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 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 is deterministic: +This SQL UDF 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 set to +`1` as the defaults for `a` and `b`. The UDF 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 it sets `a` and `b` for +the preceding to values, so it can calculate the sum, and finally return it. +Note that processing the UDF takes longer and longer with higher `n` values, and +the result is deterministic: ```sql FUNCTION fib(n bigint) @@ -246,13 +247,13 @@ SELECT fib(8); -- 21 ## Labels and loops -This routine uses the `top` label to name the `WHILE` block, and then controls +This SQL UDF 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 values `a=3`, `a=4`, `a=5`, `a=6`, and `a=7`, resulting in `b=5`. The `LEAVE` call then causes the exit of the block before a is increased further to `10` and -therefore the result of the routine is `5`: +therefore the result of the UDF is `5`: ```sql FUNCTION labels() @@ -273,9 +274,9 @@ BEGIN END ``` -This routine implements calculating the `n` to the power of `p` by repeated +This SQL UDF implements calculating the `n` to the power of `p` by repeated multiplication and keeping track of the number of multiplications performed. -Note that this routine does not return the correct `0` for `p=0` since the `top` +Note that this SQL UDF does not return the correct `0` for `p=0` since the `top` block is merely escaped and the value of `n` is returned. The same incorrect behavior happens for negative values of `p`: @@ -305,7 +306,7 @@ SELECT power(3, 0); -- 3, which is wrong SELECT power(3, -2); -- 3, which is wrong ``` -This routine returns `7` as a result of the increase of `b` in the loop from +This SQL UDF returns `7` as a result of the increase of `b` in the loop from `a=3` to `a=10`: ```sql @@ -326,7 +327,7 @@ BEGIN END ``` -This routine returns `2` and shows that labels can be repeated and label usage +This SQL UDF returns `2` and shows that labels can be repeated and label usage within a block refers to the label of that block: ```sql @@ -346,10 +347,10 @@ BEGIN END ``` -## Routines and built-in functions +## SQL UDFs and built-in functions -This routine show that multiple data types and built-in functions like -`length()` and `cardinality()` can be used in a routine. The two nested `BEGIN` +This SQL UDF shows that multiple data types and built-in functions like +`length()` and `cardinality()` can be used in a UDF. The two nested `BEGIN` blocks also show how variable names are local within these blocks `x`, but the global `r` from the top-level block can be accessed in the nested blocks: @@ -372,13 +373,13 @@ END ## Optional parameter example -Routines can invoke other routines and other functions. The full signature of a -routine is composed of the routine name and parameters, and determines the exact -routine to use. You can declare multiple routines with the same name, but with a -different number of arguments or different argument types. One example use case -is to implement an optional parameter. +UDFs can invoke other UDFs and other functions. The full signature of a UDF is +composed of the UDF name and parameters, and determines the exact UDF to use. +You can declare multiple UDFs with the same name, but with a different number of +arguments or different argument types. One example use case is to implement an +optional parameter. -The following routine truncates a string to the specified length including three +The following SQL UDF truncates a string to the specified length including three dots at the end of the output: ```sql @@ -401,8 +402,8 @@ SELECT dots('A short string',15); -- A short string ``` -If you want to provide a routine with the same name, but without the parameter -for length, you can create another routine that invokes the preceding routine: +If you want to provide a UDF with the same name, but without the parameter +for length, you can create another UDF that invokes the preceding UDF: ```sql FUNCTION dots(input varchar) @@ -410,7 +411,7 @@ RETURNS varchar RETURN dots(input, 15); ``` -You can now use both routines. When the length parameter is omitted, the default +You can now use both UDFs. When the length parameter is omitted, the default value from the second declaration is used. ```sql @@ -424,18 +425,18 @@ SELECT dots('A long string that will be shortened',20); ## Date string parsing example -This example routine parses a date string of type `VARCHAR` into `TIMESTAMP WITH +This example SQL UDF parses a date string of type `VARCHAR` into `TIMESTAMP WITH TIME ZONE`. Date strings are commonly represented by ISO 8601 standard, such as `2023-12-01`, `2023-12-01T23`. Date strings are also often represented in the `YYYYmmdd` and `YYYYmmddHH` format, such as `20230101` and `2023010123`. Hive tables can use this format to represent day and hourly partitions, for example `/day=20230101`, `/hour=2023010123`. -This routine parses date strings in a best-effort fashion and can be used as a +This UDF parses date strings in a best-effort fashion and can be used as a replacement for date string manipulation functions such as `date`, `date_parse`, `from_iso8601_date`, and `from_iso8601_timestamp`. -Note that the routine defaults the time value to `00:00:00.000` and the time +Note that the UDF defaults the time value to `00:00:00.000` and the time zone to the session time zone: ```sql @@ -475,7 +476,7 @@ SELECT human_readable_seconds(134823); -- 1 day, 13 hours, 27 minutes, 3 seconds ``` -The example routine `hrd` formats a number of days into a human readable text +The example SQL UDF `hrd` formats a number of days into a human readable text that provides the approximate number of years and months: ```sql @@ -523,16 +524,16 @@ SELECT hrd(1100); -- About 3 years SELECT hrd(5000); -- About 13 years and 8 months ``` -Improvements of the routine could include the following modifications: +Improvements of the SQL UDF could include the following modifications: * Take into account that one month equals 30.4375 days. * Take into account that one year equals 365.25 days. * Add weeks to the output. -* Expand to cover decades, centuries, and millenia. +* Expand to cover decades, centuries, and millennia. ## Truncating long strings -This example routine `strtrunc` truncates strings longer than 60 characters, +This example SQL UDF `strtrunc` truncates strings longer than 60 characters, leaving the first 30 and the last 25 characters, and cutting out extra characters in the middle: @@ -550,10 +551,10 @@ The preceding declaration is very compact and consists of only one complex statement with a [`CASE` expression](case-expression) and multiple function calls. It can therefore define the complete logic in the `RETURN` clause. -The following statement shows the same capability within the routine itself. +The following statement shows the same capability within the SQL UDF itself. Note the duplicate `RETURN` inside and outside the `CASE` statement and the -required `END CASE;`. The second `RETURN` statement is required, because a -routine must end with a `RETURN` statement. As a result the `ELSE` clause can be +required `END CASE;`. The second `RETURN` statement is required, because a SQL +UDF must end with a `RETURN` statement. As a result the `ELSE` clause can be omitted: ```sql @@ -601,8 +602,8 @@ FROM data ORDER BY data.value; ``` -The preceding query produces the following output with all variants of the -routine: +The preceding query produces the following output with all variants of the SQL +UDF: ``` value | truncated @@ -623,7 +624,7 @@ A possible improvement is to introduce parameters for the total length. ## Formatting bytes Trino includes a built-in `format_number()` function. However, it is using units -that do not work well with bytes. The following `format_data_size` routine can +that do not work well with bytes. The following `format_data_size` SQL UDF can format large values of bytes into a human readable string: ```sql @@ -745,7 +746,7 @@ The preceding query produces the following output: Trino already has a built-in `bar()` [color function](/functions/color), but it is using ANSI escape codes to output colors, and thus is only usable for -displaying results in a terminal. The following example shows a similar routine +displaying results in a terminal. The following example shows a similar SQL UDF that only uses ASCII characters: ```sql @@ -818,7 +819,7 @@ The preceding query produces the following output: 3.1 | 0.0416 | ▋ ``` -It is also possible to draw more compacted charts. Following is a routine +It is also possible to draw more compacted charts. Following is a SQL UDF drawing vertical bars: ```sql @@ -898,7 +899,7 @@ Trino already has a built-in [aggregate function](/functions/aggregate) called values. It returns a map with values as keys and number of occurrences as values. Maps are not ordered, so when displayed, the entries can change places on subsequent runs of the same query, and readers must still compare all -frequencies to find the one most frequent value. The following is a routine that +frequencies to find the one most frequent value. The following is a SQL UDF that returns ordered results as a string: ```sql diff --git a/docs/src/main/sphinx/routines/if.md b/docs/src/main/sphinx/udf/sql/if.md similarity index 56% rename from docs/src/main/sphinx/routines/if.md rename to docs/src/main/sphinx/udf/sql/if.md index 264beec66b52..626b5a2512a9 100644 --- a/docs/src/main/sphinx/routines/if.md +++ b/docs/src/main/sphinx/udf/sql/if.md @@ -14,11 +14,11 @@ END IF ## Description The `IF THEN` statement is an optional construct to allow conditional processing -in [SQL routines](/routines/introduction). Each `condition` following an `IF` -or `ELSEIF` must evaluate to a boolean. The result of processing the expression -must result in a boolean `true` value to process the `statements` in the `THEN` -block. A result of `false` results in skipping the `THEN` block and moving to -evaluate the next `ELSEIF` and `ELSE` blocks in order. +in [](/udf/sql). Each `condition` following an `IF` or `ELSEIF` must evaluate +to a boolean. The result of processing the expression must result in a boolean +`true` value to process the `statements` in the `THEN` block. A result of +`false` results in skipping the `THEN` block and moving to evaluate the next +`ELSEIF` and `ELSE` blocks in order. The `ELSEIF` and `ELSE` segments are optional. @@ -39,10 +39,9 @@ FUNCTION simple_if(a bigint) ``` Further examples of varying complexity that cover usage of the `IF` statement in -combination with other statements are available in the [SQL routines examples -documentation](/routines/examples). +combination with other statements are available in the [](/udf/sql/examples). ## See also -* [](/routines/introduction) +* [](/udf/sql) * [Conditional expressions using `IF`](if-expression) diff --git a/docs/src/main/sphinx/routines/iterate.md b/docs/src/main/sphinx/udf/sql/iterate.md similarity index 54% rename from docs/src/main/sphinx/routines/iterate.md rename to docs/src/main/sphinx/udf/sql/iterate.md index 7be395881459..59b67f2710ca 100644 --- a/docs/src/main/sphinx/routines/iterate.md +++ b/docs/src/main/sphinx/udf/sql/iterate.md @@ -8,10 +8,10 @@ ITERATE label ## Description -The `ITERATE` statement allows processing of blocks in [SQL -routines](/routines/introduction) to move processing back to the start of a -context block. Contexts are defined by a [`label`](routine-label). If no label -is found, the functions fails with an error message. +The `ITERATE` statement allows processing of blocks in [](/udf/sql) to move +processing back to the start of a context block. Contexts are defined by a +[`label`](udf-sql-label). If no label is found, the functions fails with an +error message. ## Examples @@ -32,10 +32,10 @@ END ``` Further examples of varying complexity that cover usage of the `ITERATE` -statement in combination with other statements are available in the [SQL -routines examples documentation](/routines/examples). +statement in combination with other statements are available in the +[](/udf/sql/examples). ## See also -* [](/routines/introduction) -* [](/routines/leave) +* [](/udf/sql) +* [](/udf/sql/leave) diff --git a/docs/src/main/sphinx/routines/leave.md b/docs/src/main/sphinx/udf/sql/leave.md similarity index 62% rename from docs/src/main/sphinx/routines/leave.md rename to docs/src/main/sphinx/udf/sql/leave.md index 196855509921..7697a3fc3fa7 100644 --- a/docs/src/main/sphinx/routines/leave.md +++ b/docs/src/main/sphinx/udf/sql/leave.md @@ -8,10 +8,9 @@ LEAVE label ## Description -The `LEAVE` statement allows processing of blocks in [SQL -routines](/routines/introduction) to move out of a specified context. Contexts -are defined by a [`label`](routine-label). If no label is found, the functions -fails with an error message. +The `LEAVE` statement allows processing of blocks in [](/udf/sql) to move out of +a specified context. Contexts are defined by a [`label`](udf-sql-label). If no +label is found, the functions fails with an error message. ## Examples @@ -37,10 +36,9 @@ END ``` Further examples of varying complexity that cover usage of the `LEAVE` statement -in combination with other statements are available in the [SQL routines examples -documentation](/routines/examples). +in combination with other statements are available in the [](/udf/sql/examples). ## See also -* [](/routines/introduction) -* [](/routines/iterate) +* [](/udf/sql) +* [](/udf/sql/iterate) diff --git a/docs/src/main/sphinx/routines/loop.md b/docs/src/main/sphinx/udf/sql/loop.md similarity index 80% rename from docs/src/main/sphinx/routines/loop.md rename to docs/src/main/sphinx/udf/sql/loop.md index d38918a456bf..339cea3b50b4 100644 --- a/docs/src/main/sphinx/routines/loop.md +++ b/docs/src/main/sphinx/udf/sql/loop.md @@ -10,8 +10,7 @@ END LOOP ## Description -The `LOOP` statement is an optional construct in [SQL -routines](/routines/introduction) to allow processing of a block of statements +The `LOOP` statement is an optional construct in [](/udf/sql) to allow processing of a block of statements repeatedly. The block of `statements` is processed until an explicit use of `LEAVE` causes @@ -20,7 +19,7 @@ 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 `LOOP` keyword can be used to [name the -block](routine-label). +block](udf-sql-label). ## Examples @@ -55,10 +54,10 @@ SELECT to_one_hundred(12, 3); -- 30 ``` Further examples of varying complexity that cover usage of the `LOOP` statement -in combination with other statements are available in the [SQL routines examples -documentation](/routines/examples). +in combination with other statements are available in the [SQL UDF examples +documentation](/udf/sql/examples). ## See also -* [](/routines/introduction) -* [](/routines/leave) +* [](/udf/sql) +* [](/udf/sql/leave) diff --git a/docs/src/main/sphinx/routines/repeat.md b/docs/src/main/sphinx/udf/sql/repeat.md similarity index 71% rename from docs/src/main/sphinx/routines/repeat.md rename to docs/src/main/sphinx/udf/sql/repeat.md index 5d750554c172..d75eebfec5ca 100644 --- a/docs/src/main/sphinx/routines/repeat.md +++ b/docs/src/main/sphinx/udf/sql/repeat.md @@ -11,10 +11,9 @@ END REPEAT ## Description -The `REPEAT UNTIL` statement is an optional construct in [SQL -routines](/routines/introduction) to allow processing of a block of statements -as long as a condition is met. The condition is validated as a last step of each -iteration. +The `REPEAT UNTIL` statement is an optional construct in [](/udf/sql) to allow +processing of a block of statements as long as a condition is met. The condition +is validated as a last step of each iteration. 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 @@ -22,7 +21,7 @@ subsequent processing the expression `condidtion` is validated. If the result is the function. If the result is `false`, the statements are processed again. The optional `label` before the `REPEAT` keyword can be used to [name the -block](routine-label). +block](udf-sql-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 @@ -30,7 +29,7 @@ statements might not be processed at all. ## Examples -The following routine shows a routine with a `REPEAT` statement that runs until +The following SQL UDF shows a UDF with a `REPEAT` statement that runs until the value of `a` is greater or equal to `10`. ```sql @@ -46,7 +45,7 @@ FUNCTION test_repeat(a bigint) ``` Since `a` is also the input value and it is increased before the check the -routine always returns `10` for input values of `9` or less, and the input value +UDF always returns `10` for input values of `9` or less, and the input value + 1 for all higher values. Following are a couple of example invocations with result and explanation: @@ -60,11 +59,11 @@ SELECT test_repeat(12); -- 13 ``` Further examples of varying complexity that cover usage of the `REPEAT` -statement in combination with other statements are available in the [SQL -routines examples documentation](/routines/examples). +statement in combination with other statements are available in the +[](/udf/sql/examples). ## See also -* [](/routines/introduction) -* [](/routines/loop) -* [](/routines/while) +* [](/udf/sql) +* [](/udf/sql/loop) +* [](/udf/sql/while) diff --git a/docs/src/main/sphinx/udf/sql/return.md b/docs/src/main/sphinx/udf/sql/return.md new file mode 100644 index 000000000000..2044b17466b0 --- /dev/null +++ b/docs/src/main/sphinx/udf/sql/return.md @@ -0,0 +1,36 @@ +# RETURN + +## Synopsis + +```text +RETURN expression +``` + +## Description + +Provide the value from a [](/udf/sql) to the caller. The value is the result of +evaluating the expression. It can be a static value, a declared variable or a +more complex expression. + +## Examples + +The following examples return a static value, the result of an expression, and +the value of the variable x: + +```sql +RETURN 42; +RETURN 6 * 7; +RETURN x; +``` + +Further examples of varying complexity that cover usage of the `RETURN` +statement in combination with other statements are available in the +[](/udf/sql/examples). + +All SQL UDFs must contain a `RETURN` statement at the end of the top-level block +in the `FUNCTION` declaration, even if it's unreachable. + +## See also + +* [](/udf/sql) +* [](/udf/function) diff --git a/docs/src/main/sphinx/routines/set.md b/docs/src/main/sphinx/udf/sql/set.md similarity index 63% rename from docs/src/main/sphinx/routines/set.md rename to docs/src/main/sphinx/udf/sql/set.md index d9c24414e049..971ac5772e59 100644 --- a/docs/src/main/sphinx/routines/set.md +++ b/docs/src/main/sphinx/udf/sql/set.md @@ -8,9 +8,9 @@ SET identifier = expression ## Description -Use the `SET` statement in [SQL routines](/routines/introduction) to assign a -value to a variable, referenced by comma-separated `identifier`s. The -value is determined by evaluating the `expression` after the `=` sign. +Use the `SET` statement in [](/udf/sql) to assign a value to a variable, +referenced by comma-separated `identifier`s. The value is determined by +evaluating the `expression` after the `=` sign. Before the assignment the variable must be defined with a `DECLARE` statement. The data type of the variable must be identical to the data type of evaluating @@ -34,10 +34,9 @@ FUNCTION one() ``` Further examples of varying complexity that cover usage of the `SET` statement -in combination with other statements are available in the [SQL routines examples -documentation](/routines/examples). +in combination with other statements are available in the [](/udf/sql/examples). ## See also -* [](/routines/introduction) -* [](/routines/declare) +* [](/udf/sql) +* [](/udf/sql/declare) diff --git a/docs/src/main/sphinx/routines/while.md b/docs/src/main/sphinx/udf/sql/while.md similarity index 66% rename from docs/src/main/sphinx/routines/while.md rename to docs/src/main/sphinx/udf/sql/while.md index 6e252482d4b8..74f4b143659e 100644 --- a/docs/src/main/sphinx/routines/while.md +++ b/docs/src/main/sphinx/udf/sql/while.md @@ -10,10 +10,9 @@ END WHILE ## Description -The `WHILE` statement is an optional construct in [SQL -routines](/routines/introduction) to allow processing of a block of statements -as long as a condition is met. The condition is validated as a first step of -each iteration. +The `WHILE` statement is an optional construct in [](/udf/sql) to allow +processing of a block of statements as long as a condition is met. The condition +is validated as a first step of each iteration. The expression that defines the `condition` is evaluated at least once. If the result is `true`, processing moves to `DO`, through following `statements` and @@ -21,7 +20,7 @@ back to `WHILE` and the `condition`. If the result is `false`, processing moves to `END WHILE` and continues with the next statement in the function. The optional `label` before the `WHILE` keyword can be used to [name the -block](routine-label). +block](udf-sql-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 @@ -37,11 +36,10 @@ END WHILE; ``` Further examples of varying complexity that cover usage of the `WHILE` statement -in combination with other statements are available in the [SQL routines examples -documentation](/routines/examples). +in combination with other statements are available in the [](/udf/sql/examples). ## See also -* [](/routines/introduction) -* [](/routines/loop) -* [](/routines/repeat) +* [](/udf/sql) +* [](/udf/sql/loop) +* [](/udf/sql/repeat) diff --git a/lib/trino-array/pom.xml b/lib/trino-array/pom.xml index 9cd91d5fa696..770af98fada8 100644 --- a/lib/trino-array/pom.xml +++ b/lib/trino-array/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-cache/pom.xml b/lib/trino-cache/pom.xml index 0ae4a50d484e..49d3b1c68de9 100644 --- a/lib/trino-cache/pom.xml +++ b/lib/trino-cache/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-alluxio/pom.xml b/lib/trino-filesystem-alluxio/pom.xml index db27a0db7c9e..7adc50ffd260 100644 --- a/lib/trino-filesystem-alluxio/pom.xml +++ b/lib/trino-filesystem-alluxio/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileIterator.java b/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileIterator.java index e67a51f16e90..d20bc4cc2aff 100644 --- a/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileIterator.java +++ b/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileIterator.java @@ -24,19 +24,18 @@ import java.util.List; import java.util.Optional; -import static io.trino.filesystem.alluxio.AlluxioUtils.convertToLocation; import static java.util.Objects.requireNonNull; public class AlluxioFileIterator implements FileIterator { private final Iterator files; - private final String mountRoot; + private final String basePath; - public AlluxioFileIterator(List files, String mountRoot) + public AlluxioFileIterator(List files, String basePath) { this.files = requireNonNull(files.iterator(), "files is null"); - this.mountRoot = requireNonNull(mountRoot, "mountRoot is null"); + this.basePath = requireNonNull(basePath, "basePath is null"); } @Override @@ -54,7 +53,8 @@ public FileEntry next() return null; } URIStatus fileStatus = files.next(); - Location location = convertToLocation(fileStatus.getPath(), mountRoot); + String filePath = fileStatus.getPath(); + Location location = Location.of(basePath + filePath); return new FileEntry( location, fileStatus.getLength(), diff --git a/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileSystem.java b/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileSystem.java index b6a949ee44d6..94654da3f1b2 100644 --- a/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileSystem.java +++ b/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileSystem.java @@ -38,6 +38,7 @@ import java.util.stream.Collectors; import static io.trino.filesystem.alluxio.AlluxioUtils.convertToAlluxioURI; +import static io.trino.filesystem.alluxio.AlluxioUtils.getAlluxioBase; import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; @@ -189,20 +190,20 @@ public FileIterator listFiles(Location location) try { URIStatus status = alluxioClient.getStatus(convertToAlluxioURI(location, mountRoot)); if (status == null) { - new AlluxioFileIterator(Collections.emptyList(), mountRoot); + new AlluxioFileIterator(Collections.emptyList(), getAlluxioBase(location.toString())); } if (!status.isFolder()) { throw new IOException("Location is not a directory: %s".formatted(location)); } } catch (NotFoundRuntimeException | AlluxioException e) { - return new AlluxioFileIterator(Collections.emptyList(), mountRoot); + return new AlluxioFileIterator(Collections.emptyList(), getAlluxioBase(location.toString())); } try { List filesStatus = alluxioClient.listStatus(convertToAlluxioURI(location, mountRoot), ListStatusPOptions.newBuilder().setRecursive(true).build()); - return new AlluxioFileIterator(filesStatus.stream().filter(status -> !status.isFolder() & status.isCompleted()).toList(), mountRoot); + return new AlluxioFileIterator(filesStatus.stream().filter(status -> !status.isFolder() & status.isCompleted()).toList(), getAlluxioBase(location.toString())); } catch (AlluxioException e) { throw new IOException("Error listFiles %s".formatted(location), e); diff --git a/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioUtils.java b/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioUtils.java index 49ece36a8b93..ac95c970dd36 100644 --- a/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioUtils.java +++ b/lib/trino-filesystem-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioUtils.java @@ -50,6 +50,16 @@ public static Location convertToLocation(String path, String mountRoot) return Location.of(schema + mountRootWithSlash + path); } + public static String getAlluxioBase(String path) + { + requireNonNull(path, "path is null"); + if (!path.startsWith("alluxio://")) { + throw new IllegalArgumentException("path is not an alluxio://"); + } + int index = path.indexOf('/', "alluxio://".length()); + return path.substring(0, index); + } + public static String simplifyPath(String path) { // Use a deque to store the path components diff --git a/lib/trino-filesystem-alluxio/src/test/java/io/trino/filesystem/alluxio/TestAlluxioFileIterator.java b/lib/trino-filesystem-alluxio/src/test/java/io/trino/filesystem/alluxio/TestAlluxioFileIterator.java new file mode 100644 index 000000000000..6ae971673887 --- /dev/null +++ b/lib/trino-filesystem-alluxio/src/test/java/io/trino/filesystem/alluxio/TestAlluxioFileIterator.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.filesystem.alluxio; + +import alluxio.client.file.URIStatus; +import alluxio.wire.FileInfo; +import io.trino.filesystem.FileEntry; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +final class TestAlluxioFileIterator +{ + private final String fileName = "000000_0"; + private final String filePath = "/s3a/tables/sales/000000_0"; + private final String ufsFilePath = "s3a://test-bucket/tables/sales/000000_0"; + + @Test + void testNext() + throws IOException + { + String alluxioBasePath = "alluxio://master:19998"; + FileInfo fileInfo = new FileInfo(); + fileInfo.setName(fileName); + fileInfo.setPath(filePath); + fileInfo.setUfsPath(ufsFilePath); + URIStatus fileStatus = new URIStatus(fileInfo); + AlluxioFileIterator iterator = new AlluxioFileIterator( + List.of(fileStatus), + alluxioBasePath); + FileEntry fileEntry = iterator.next(); + assertThat(fileEntry.location().toString()) + .isEqualTo(alluxioBasePath + filePath); + + alluxioBasePath = "alluxio:/"; + fileInfo = new FileInfo(); + fileInfo.setName(fileName); + fileInfo.setPath(filePath); + fileInfo.setUfsPath(ufsFilePath); + fileStatus = new URIStatus(fileInfo); + iterator = new AlluxioFileIterator( + List.of(fileStatus), + alluxioBasePath); + fileEntry = iterator.next(); + assertThat(fileEntry.location().toString()) + .isEqualTo(alluxioBasePath + filePath); + } +} diff --git a/lib/trino-filesystem-azure/pom.xml b/lib/trino-filesystem-azure/pom.xml index 8fad433ce06f..e67100cd4db4 100644 --- a/lib/trino-filesystem-azure/pom.xml +++ b/lib/trino-filesystem-azure/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -178,6 +178,12 @@ test + + io.trino + trino-testing-services + test + + org.assertj assertj-core 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 5b5afa9dc3c4..5d87efbc5623 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 @@ -53,11 +53,6 @@ public abstract class AbstractTestAzureFileSystem { private final EncryptionKey key = EncryptionKey.randomAes256(); - protected static String getRequiredEnvironmentVariable(String name) - { - return requireNonNull(System.getenv(name), "Environment variable not set: " + name); - } - protected enum AccountKind { HIERARCHICAL, FLAT diff --git a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemGen2Flat.java b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemGen2Flat.java index cc3233764284..def79fb20435 100644 --- a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemGen2Flat.java +++ b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemGen2Flat.java @@ -20,6 +20,7 @@ import java.io.IOException; import static io.trino.filesystem.azure.AbstractTestAzureFileSystem.AccountKind.FLAT; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestInstance(Lifecycle.PER_CLASS) class TestAzureFileSystemGen2Flat @@ -29,6 +30,6 @@ class TestAzureFileSystemGen2Flat void setup() throws IOException { - initializeWithAccessKey(getRequiredEnvironmentVariable("ABFS_FLAT_ACCOUNT"), getRequiredEnvironmentVariable("ABFS_FLAT_ACCESS_KEY"), FLAT); + initializeWithAccessKey(requireEnv("ABFS_FLAT_ACCOUNT"), requireEnv("ABFS_FLAT_ACCESS_KEY"), FLAT); } } diff --git a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemGen2Hierarchical.java b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemGen2Hierarchical.java index 5efb95e40ec1..d8423e00c2b5 100644 --- a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemGen2Hierarchical.java +++ b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemGen2Hierarchical.java @@ -20,6 +20,7 @@ import java.io.IOException; import static io.trino.filesystem.azure.AbstractTestAzureFileSystem.AccountKind.HIERARCHICAL; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestInstance(Lifecycle.PER_CLASS) class TestAzureFileSystemGen2Hierarchical @@ -29,6 +30,6 @@ class TestAzureFileSystemGen2Hierarchical void setup() throws IOException { - initializeWithAccessKey(getRequiredEnvironmentVariable("ABFS_HIERARCHICAL_ACCOUNT"), getRequiredEnvironmentVariable("ABFS_HIERARCHICAL_ACCESS_KEY"), HIERARCHICAL); + initializeWithAccessKey(requireEnv("ABFS_HIERARCHICAL_ACCOUNT"), requireEnv("ABFS_HIERARCHICAL_ACCESS_KEY"), HIERARCHICAL); } } diff --git a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemOAuthGen2Flat.java b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemOAuthGen2Flat.java index 82c3ac4efb55..ff70e01f44c5 100644 --- a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemOAuthGen2Flat.java +++ b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemOAuthGen2Flat.java @@ -20,6 +20,7 @@ import java.io.IOException; import static io.trino.filesystem.azure.AbstractTestAzureFileSystem.AccountKind.FLAT; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestInstance(Lifecycle.PER_CLASS) public class TestAzureFileSystemOAuthGen2Flat @@ -29,10 +30,10 @@ public class TestAzureFileSystemOAuthGen2Flat void setup() throws IOException { - String account = getRequiredEnvironmentVariable("ABFS_FLAT_ACCOUNT"); - String tenantId = getRequiredEnvironmentVariable("ABFS_OAUTH_TENANT_ID"); - String clientId = getRequiredEnvironmentVariable("ABFS_OAUTH_CLIENT_ID"); - String clientSecret = getRequiredEnvironmentVariable("ABFS_OAUTH_CLIENT_SECRET"); + String account = requireEnv("ABFS_FLAT_ACCOUNT"); + String tenantId = requireEnv("ABFS_OAUTH_TENANT_ID"); + String clientId = requireEnv("ABFS_OAUTH_CLIENT_ID"); + String clientSecret = requireEnv("ABFS_OAUTH_CLIENT_SECRET"); initializeWithOAuth(account, tenantId, clientId, clientSecret, FLAT); } } diff --git a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemOAuthGen2Hierarchical.java b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemOAuthGen2Hierarchical.java index 0276a6542b25..1ac28bed2d84 100644 --- a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemOAuthGen2Hierarchical.java +++ b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/TestAzureFileSystemOAuthGen2Hierarchical.java @@ -20,6 +20,7 @@ import java.io.IOException; import static io.trino.filesystem.azure.AbstractTestAzureFileSystem.AccountKind.HIERARCHICAL; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestInstance(Lifecycle.PER_CLASS) public class TestAzureFileSystemOAuthGen2Hierarchical @@ -29,10 +30,10 @@ public class TestAzureFileSystemOAuthGen2Hierarchical void setup() throws IOException { - String account = getRequiredEnvironmentVariable("ABFS_HIERARCHICAL_ACCOUNT"); - String tenantId = getRequiredEnvironmentVariable("ABFS_OAUTH_TENANT_ID"); - String clientId = getRequiredEnvironmentVariable("ABFS_OAUTH_CLIENT_ID"); - String clientSecret = getRequiredEnvironmentVariable("ABFS_OAUTH_CLIENT_SECRET"); + String account = requireEnv("ABFS_HIERARCHICAL_ACCOUNT"); + String tenantId = requireEnv("ABFS_OAUTH_TENANT_ID"); + String clientId = requireEnv("ABFS_OAUTH_CLIENT_ID"); + String clientSecret = requireEnv("ABFS_OAUTH_CLIENT_SECRET"); initializeWithOAuth(account, tenantId, clientId, clientSecret, HIERARCHICAL); } } diff --git a/lib/trino-filesystem-cache-alluxio/pom.xml b/lib/trino-filesystem-cache-alluxio/pom.xml index 93a491906c03..f169d718b470 100644 --- a/lib/trino-filesystem-cache-alluxio/pom.xml +++ b/lib/trino-filesystem-cache-alluxio/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -63,6 +63,11 @@ trino-filesystem + + io.trino + trino-spi + + jakarta.annotation jakarta.annotation-api @@ -112,12 +117,6 @@ runtime - - io.trino - trino-spi - runtime - - io.airlift junit-extensions diff --git a/lib/trino-filesystem-cache-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileSystemCacheModule.java b/lib/trino-filesystem-cache-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileSystemCacheModule.java index d4acfe96d4c8..4d063e05e5d9 100644 --- a/lib/trino-filesystem-cache-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileSystemCacheModule.java +++ b/lib/trino-filesystem-cache-alluxio/src/main/java/io/trino/filesystem/alluxio/AlluxioFileSystemCacheModule.java @@ -16,11 +16,13 @@ import alluxio.metrics.MetricsConfig; import alluxio.metrics.MetricsSystem; import com.google.inject.Binder; +import com.google.inject.Provider; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.trino.filesystem.cache.CachingHostAddressProvider; import io.trino.filesystem.cache.ConsistentHashingHostAddressProvider; import io.trino.filesystem.cache.ConsistentHashingHostAddressProviderConfig; import io.trino.filesystem.cache.TrinoFileSystemCache; +import io.trino.spi.catalog.CatalogName; import java.util.Properties; @@ -45,7 +47,9 @@ protected void setup(Binder binder) configBinder(binder).bindConfig(AlluxioFileSystemCacheConfig.class); configBinder(binder).bindConfig(ConsistentHashingHostAddressProviderConfig.class); binder.bind(AlluxioCacheStats.class).in(SINGLETON); - newExporter(binder).export(AlluxioCacheStats.class).as(generator -> generator.generatedNameOf(AlluxioCacheStats.class)); + Provider catalogName = binder.getProvider(CatalogName.class); + newExporter(binder).export(AlluxioCacheStats.class) + .as(generator -> generator.generatedNameOf(AlluxioCacheStats.class, catalogName.get().toString())); if (isCoordinator) { newOptionalBinder(binder, CachingHostAddressProvider.class).setBinding().to(ConsistentHashingHostAddressProvider.class).in(SINGLETON); diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml index e06cf563005e..e7c399254847 100644 --- a/lib/trino-filesystem-gcs/pom.xml +++ b/lib/trino-filesystem-gcs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 18cb104e348c..f78d1a51e658 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 @@ -26,6 +26,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import java.util.Optional; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkState; @@ -39,6 +40,7 @@ public class GcsFileSystemConfig private int batchSize = 100; private String projectId; + private Optional endpoint = Optional.empty(); private boolean useGcsAccessToken; private String jsonKey; @@ -120,6 +122,19 @@ public GcsFileSystemConfig setProjectId(String projectId) return this; } + public Optional getEndpoint() + { + return endpoint; + } + + @ConfigDescription("Endpoint to use for GCS requests") + @Config("gcs.endpoint") + public GcsFileSystemConfig setEndpoint(Optional endpoint) + { + this.endpoint = endpoint; + return this; + } + public boolean isUseGcsAccessToken() { return useGcsAccessToken; 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 f57dcb9f32dd..f8ea12c4520a 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 @@ -41,6 +41,7 @@ public class 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 Optional endpoint; private final boolean useGcsAccessToken; private final Optional jsonGoogleCredential; private final int maxRetries; @@ -56,6 +57,7 @@ public GcsStorageFactory(GcsFileSystemConfig config) { config.validate(); projectId = config.getProjectId(); + endpoint = config.getEndpoint(); useGcsAccessToken = config.isUseGcsAccessToken(); String jsonKey = config.getJsonKey(); String jsonKeyFilePath = config.getJsonKeyFilePath(); @@ -105,6 +107,9 @@ public Storage create(ConnectorIdentity identity) if (projectId != null) { storageOptionsBuilder.setProjectId(projectId); } + + endpoint.ifPresent(storageOptionsBuilder::setHost); + // Note: without uniform strategy we cannot retry idempotent operations. // The trino-filesystem api does not violate the conditions for idempotency, see https://cloud.google.com/storage/docs/retry-strategy#java for details. return storageOptionsBuilder 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 d17b1d64571a..128f634fb3f1 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 @@ -35,7 +35,6 @@ import static com.google.cloud.storage.Storage.BlobTargetOption.doesNotExist; import static io.trino.filesystem.encryption.EncryptionKey.randomAes256; 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) @@ -48,11 +47,6 @@ public abstract class AbstractTestGcsFileSystem 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 { diff --git a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystem.java b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystem.java index 177e1390de20..0213d77c32af 100644 --- a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystem.java +++ b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystem.java @@ -20,6 +20,7 @@ import java.io.IOException; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static java.nio.charset.StandardCharsets.UTF_8; @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -30,7 +31,7 @@ public class TestGcsFileSystem void setup() throws IOException { - initialize(getRequiredEnvironmentVariable("GCP_CREDENTIALS_KEY")); + initialize(requireEnv("GCP_CREDENTIALS_KEY")); } @Test 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 22ab1f7f6212..fd9f5cddfd70 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 @@ -23,6 +23,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; +import java.util.Optional; import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; @@ -44,6 +45,7 @@ void testDefaults() .setPageSize(100) .setBatchSize(100) .setProjectId(null) + .setEndpoint(Optional.empty()) .setUseGcsAccessToken(false) .setJsonKey(null) .setJsonKeyFilePath(null) @@ -67,6 +69,7 @@ void testExplicitPropertyMappings() .put("gcs.page-size", "10") .put("gcs.batch-size", "11") .put("gcs.project-id", "project") + .put("gcs.endpoint", "http://custom.dns.org:8000") .put("gcs.use-access-token", "true") .put("gcs.json-key", "{}") .put("gcs.json-key-file-path", jsonKeyFile.toString()) @@ -84,6 +87,7 @@ void testExplicitPropertyMappings() .setPageSize(10) .setBatchSize(11) .setProjectId("project") + .setEndpoint(Optional.of("http://custom.dns.org:8000")) .setUseGcsAccessToken(true) .setJsonKey("{}") .setJsonKeyFilePath(jsonKeyFile.toString()) diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index bc27cd26088b..c12f2086d9d5 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-s3/pom.xml b/lib/trino-filesystem-s3/pom.xml index 882a0005422c..4d767d05c5ca 100644 --- a/lib/trino-filesystem-s3/pom.xml +++ b/lib/trino-filesystem-s3/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -239,6 +239,12 @@ test + + io.trino + trino-testing-services + test + + org.assertj assertj-core diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3Context.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3Context.java index eeb33295d832..cc9dbf4ffeeb 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3Context.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3Context.java @@ -25,6 +25,7 @@ import java.util.Optional; import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.filesystem.s3.S3FileSystemConfig.S3SseType.CUSTOMER; import static io.trino.filesystem.s3.S3FileSystemConfig.S3SseType.KMS; import static io.trino.filesystem.s3.S3FileSystemConstants.EXTRA_CREDENTIALS_ACCESS_KEY_PROPERTY; import static io.trino.filesystem.s3.S3FileSystemConstants.EXTRA_CREDENTIALS_SECRET_KEY_PROPERTY; @@ -70,6 +71,11 @@ public S3Context withCredentials(ConnectorIdentity identity) return this; } + public S3Context withSseCustomerKey(String key) + { + return new S3Context(partSize, requesterPays, S3SseContext.withSseCustomerKey(key), credentialsProviderOverride, cannedAcl, exclusiveWriteSupported); + } + public S3Context withCredentialsProviderOverride(AwsCredentialsProvider credentialsProviderOverride) { return new S3Context( @@ -109,5 +115,10 @@ public static S3SseContext withKmsKeyId(String kmsKeyId) { return new S3SseContext(KMS, Optional.ofNullable(kmsKeyId), Optional.empty()); } + + public static S3SseContext withSseCustomerKey(String key) + { + return new S3SseContext(CUSTOMER, Optional.empty(), Optional.ofNullable(key).map(S3SseCustomerKey::onAes256)); + } } } diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemLoader.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemLoader.java index bb011a336f5c..be1531030181 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemLoader.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemLoader.java @@ -46,6 +46,7 @@ import java.util.concurrent.ExecutorService; import java.util.function.Function; +import static com.google.common.base.Preconditions.checkState; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static io.trino.filesystem.s3.S3FileSystemConfig.RetryMode.getRetryStrategy; import static java.lang.Math.toIntExact; @@ -108,9 +109,14 @@ public TrinoFileSystemFactory apply(Location location) S3Context context = this.context.withCredentials(identity); if (mapping.isPresent() && mapping.get().kmsKeyId().isPresent()) { + checkState(mapping.get().sseCustomerKey().isEmpty(), "Both SSE-C and KMS-managed keys cannot be used at the same time"); context = context.withKmsKeyId(mapping.get().kmsKeyId().get()); } + if (mapping.isPresent() && mapping.get().sseCustomerKey().isPresent()) { + context = context.withSseCustomerKey(mapping.get().sseCustomerKey().get()); + } + return new S3FileSystem(uploadExecutor, client, preSigner, context); }; } diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3InputStream.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3InputStream.java index 6bcbc6e128ee..0122cf1093a8 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3InputStream.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3InputStream.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.io.InterruptedIOException; +import static java.lang.Math.clamp; import static java.lang.Math.max; import static java.util.Objects.requireNonNull; @@ -126,14 +127,10 @@ public long skip(long n) throws IOException { ensureOpen(); - seekStream(false); - return reconnectStreamIfNecessary(() -> { - long skip = doSkip(n); - streamPosition += skip; - nextReadPosition += skip; - return skip; - }); + long skipSize = clamp(n, 0, length != null ? length - nextReadPosition : Integer.MAX_VALUE); + nextReadPosition += skipSize; + return skipSize; } @Override diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMapping.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMapping.java index 4b65e5fce887..064040d06295 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMapping.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMapping.java @@ -42,6 +42,8 @@ public final class S3SecurityMapping private final Set allowedIamRoles; private final Optional kmsKeyId; private final Set allowedKmsKeyIds; + private final Optional sseCustomerKey; + private final Set allowedSseCustomerKeys; private final Optional credentials; private final boolean useClusterDefault; private final Optional endpoint; @@ -57,6 +59,8 @@ public S3SecurityMapping( @JsonProperty("allowedIamRoles") Optional> allowedIamRoles, @JsonProperty("kmsKeyId") Optional kmsKeyId, @JsonProperty("allowedKmsKeyIds") Optional> allowedKmsKeyIds, + @JsonProperty("sseCustomerKey") Optional sseCustomerKey, + @JsonProperty("allowedSseCustomerKeys") Optional> allowedSseCustomerKeys, @JsonProperty("accessKey") Optional accessKey, @JsonProperty("secretKey") Optional secretKey, @JsonProperty("useClusterDefault") Optional useClusterDefault, @@ -86,6 +90,10 @@ public S3SecurityMapping( this.allowedKmsKeyIds = ImmutableSet.copyOf(allowedKmsKeyIds.orElse(ImmutableList.of())); + this.sseCustomerKey = requireNonNull(sseCustomerKey, "sseCustomerKey is null"); + + this.allowedSseCustomerKeys = allowedSseCustomerKeys.map(ImmutableSet::copyOf).orElse(ImmutableSet.of()); + requireNonNull(accessKey, "accessKey is null"); requireNonNull(secretKey, "secretKey is null"); checkArgument(accessKey.isPresent() == secretKey.isPresent(), "accessKey and secretKey must be provided together"); @@ -96,6 +104,8 @@ public S3SecurityMapping( checkArgument(this.useClusterDefault != roleOrCredentialsArePresent, "must either allow useClusterDefault role or provide role and/or credentials"); checkArgument(!this.useClusterDefault || this.kmsKeyId.isEmpty(), "KMS key ID cannot be provided together with useClusterDefault"); + checkArgument(!this.useClusterDefault || this.sseCustomerKey.isEmpty(), "SSE Customer key cannot be provided together with useClusterDefault"); + checkArgument(this.kmsKeyId.isEmpty() || this.sseCustomerKey.isEmpty(), "SSE Customer key cannot be provided together with KMS key ID"); this.endpoint = requireNonNull(endpoint, "endpoint is null"); this.region = requireNonNull(region, "region is null"); @@ -133,6 +143,16 @@ public Set allowedKmsKeyIds() return allowedKmsKeyIds; } + public Optional sseCustomerKey() + { + return sseCustomerKey; + } + + public Set allowedSseCustomerKeys() + { + return allowedSseCustomerKeys; + } + public Optional credentials() { return credentials; diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingConfig.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingConfig.java index 2f1d6062f7cb..8bf2b8cbe499 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingConfig.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingConfig.java @@ -31,6 +31,7 @@ public class S3SecurityMappingConfig private String jsonPointer = ""; private String roleCredentialName; private String kmsKeyIdCredentialName; + private String sseCustomerKeyCredentialName; private Duration refreshPeriod; private String colonReplacement; @@ -100,6 +101,19 @@ public S3SecurityMappingConfig setKmsKeyIdCredentialName(String kmsKeyIdCredenti return this; } + public Optional getSseCustomerKeyCredentialName() + { + return Optional.ofNullable(sseCustomerKeyCredentialName); + } + + @Config("s3.security-mapping.sse-customer-key-credential-name") + @ConfigDescription("Name of the extra credential used to provide SSE Customer key") + public S3SecurityMappingConfig setSseCustomerKeyCredentialName(String sseCustomerKeyCredentialName) + { + this.sseCustomerKeyCredentialName = sseCustomerKeyCredentialName; + return this; + } + public Optional getRefreshPeriod() { return Optional.ofNullable(refreshPeriod); diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingProvider.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingProvider.java index e0823c2c5fcc..008fb835f6bf 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingProvider.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingProvider.java @@ -33,6 +33,7 @@ final class S3SecurityMappingProvider private final Supplier mappingsProvider; private final Optional roleCredentialName; private final Optional kmsKeyIdCredentialName; + private final Optional sseCustomerKeyCredentialName; private final Optional colonReplacement; @Inject @@ -41,6 +42,7 @@ public S3SecurityMappingProvider(S3SecurityMappingConfig config, Supplier mappingsProvider, Optional roleCredentialName, Optional kmsKeyIdCredentialName, + Optional sseCustomerKeyCredentialName, Optional colonReplacement) { this.mappingsProvider = requireNonNull(mappingsProvider, "mappingsProvider is null"); this.roleCredentialName = requireNonNull(roleCredentialName, "roleCredentialName is null"); this.kmsKeyIdCredentialName = requireNonNull(kmsKeyIdCredentialName, "kmsKeyIdCredentialName is null"); + this.sseCustomerKeyCredentialName = requireNonNull(sseCustomerKeyCredentialName, "customerKeyCredentialName is null"); this.colonReplacement = requireNonNull(colonReplacement, "colonReplacement is null"); } @@ -70,6 +74,7 @@ public Optional getMapping(ConnectorIdentity identity, selectRole(mapping, identity), mapping.roleSessionName().map(name -> name.replace("${USER}", identity.getUser())), selectKmsKeyId(mapping, identity), + getSseCustomerKey(mapping, identity), mapping.endpoint(), mapping.region())); } @@ -131,6 +136,32 @@ private Optional getKmsKeyIdFromExtraCredential(ConnectorIdentity identi return kmsKeyIdCredentialName.map(name -> identity.getExtraCredentials().get(name)); } + private Optional getSseCustomerKey(S3SecurityMapping mapping, ConnectorIdentity identity) + { + Optional providedKey = getSseCustomerKeyFromExtraCredential(identity); + + if (providedKey.isEmpty()) { + return mapping.sseCustomerKey(); + } + if (mapping.sseCustomerKey().isPresent() && mapping.allowedSseCustomerKeys().isEmpty()) { + throw new AccessDeniedException("allowedSseCustomerKeys must be set if sseCustomerKey is provided"); + } + + String selected = providedKey.get(); + + if (selected.equals(mapping.sseCustomerKey().orElse(null)) || + mapping.allowedSseCustomerKeys().contains(selected) || + mapping.allowedSseCustomerKeys().contains("*")) { + return providedKey; + } + throw new AccessDeniedException("Provided SSE Customer Key is not allowed"); + } + + private Optional getSseCustomerKeyFromExtraCredential(ConnectorIdentity identity) + { + return sseCustomerKeyCredentialName.map(name -> identity.getExtraCredentials().get(name)); + } + private static Supplier mappingsProvider(Supplier supplier, Optional refreshPeriod) { return refreshPeriod diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingResult.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingResult.java index 23514f2d1a83..2d2ef7dc2e92 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingResult.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3SecurityMappingResult.java @@ -26,6 +26,7 @@ record S3SecurityMappingResult( Optional iamRole, Optional roleSessionName, Optional kmsKeyId, + Optional sseCustomerKey, Optional endpoint, Optional region) { @@ -35,6 +36,7 @@ record S3SecurityMappingResult( requireNonNull(iamRole, "iamRole is null"); requireNonNull(roleSessionName, "roleSessionName is null"); requireNonNull(kmsKeyId, "kmsKeyId is null"); + requireNonNull(sseCustomerKey, "sseCustomerKey is null"); requireNonNull(endpoint, "endpoint is null"); requireNonNull(region, "region is null"); } 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 200732d4499c..5d578543aedb 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 @@ -29,7 +29,7 @@ import java.util.List; import static com.google.common.collect.Iterables.getOnlyElement; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static org.assertj.core.api.Assertions.assertThat; public class TestS3FileSystemAwsS3 @@ -43,10 +43,10 @@ public class TestS3FileSystemAwsS3 @Override protected void initEnvironment() { - accessKey = environmentVariable("AWS_ACCESS_KEY_ID"); - secretKey = environmentVariable("AWS_SECRET_ACCESS_KEY"); - region = environmentVariable("AWS_REGION"); - bucket = environmentVariable("EMPTY_S3_BUCKET"); + accessKey = requireEnv("AWS_ACCESS_KEY_ID"); + secretKey = requireEnv("AWS_SECRET_ACCESS_KEY"); + region = requireEnv("AWS_REGION"); + bucket = requireEnv("EMPTY_S3_BUCKET"); } @Override @@ -75,11 +75,6 @@ protected S3FileSystemFactory createS3FileSystemFactory() .setStreamingPartSize(DataSize.valueOf("5.5MB")), new S3FileSystemStats()); } - private static String environmentVariable(String name) - { - return requireNonNull(System.getenv(name), "Environment variable not set: " + name); - } - @Test void testS3FileIteratorFileEntryTags() throws IOException diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemAwsS3WithSseCustomerKey.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemAwsS3WithSseCustomerKey.java index 7dcdaba12f8e..3de274317995 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemAwsS3WithSseCustomerKey.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemAwsS3WithSseCustomerKey.java @@ -33,7 +33,7 @@ import java.util.function.Function; import static io.trino.filesystem.s3.S3FileSystemConfig.S3SseType.CUSTOMER; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; public class TestS3FileSystemAwsS3WithSseCustomerKey extends AbstractTestS3FileSystem @@ -49,10 +49,10 @@ public class TestS3FileSystemAwsS3WithSseCustomerKey @Override protected void initEnvironment() { - accessKey = environmentVariable("AWS_ACCESS_KEY_ID"); - secretKey = environmentVariable("AWS_SECRET_ACCESS_KEY"); - region = environmentVariable("AWS_REGION"); - bucket = environmentVariable("EMPTY_S3_BUCKET"); + accessKey = requireEnv("AWS_ACCESS_KEY_ID"); + secretKey = requireEnv("AWS_SECRET_ACCESS_KEY"); + region = requireEnv("AWS_REGION"); + bucket = requireEnv("EMPTY_S3_BUCKET"); s3SseCustomerKey = S3SseCustomerKey.onAes256(CUSTOMER_KEY); } @@ -109,11 +109,6 @@ protected S3FileSystemFactory createS3FileSystemFactory() new S3FileSystemStats()); } - private static String environmentVariable(String name) - { - return requireNonNull(System.getenv(name), "Environment variable not set: " + name); - } - private static String generateCustomerKey() { try { diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3SecurityMapping.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3SecurityMapping.java index f301a83d9234..b3b0809ec581 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3SecurityMapping.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3SecurityMapping.java @@ -37,6 +37,7 @@ public class TestS3SecurityMapping { private static final String IAM_ROLE_CREDENTIAL_NAME = "IAM_ROLE_CREDENTIAL_NAME"; private static final String KMS_KEY_ID_CREDENTIAL_NAME = "KMS_KEY_ID_CREDENTIAL_NAME"; + private static final String CUSTOMER_KEY_CREDENTIAL_NAME = "CUSTOMER_KEY_CREDENTIAL_NAME"; private static final String DEFAULT_PATH = "s3://default/"; private static final String DEFAULT_USER = "testuser"; @@ -47,6 +48,7 @@ public void testMapping() .setConfigFile(getResourceFile("security-mapping.json")) .setRoleCredentialName(IAM_ROLE_CREDENTIAL_NAME) .setKmsKeyIdCredentialName(KMS_KEY_ID_CREDENTIAL_NAME) + .setSseCustomerKeyCredentialName(CUSTOMER_KEY_CREDENTIAL_NAME) .setColonReplacement("#"); var provider = new S3SecurityMappingProvider(mappingConfig, new S3SecurityMappingsFileSource(mappingConfig)); @@ -104,6 +106,45 @@ public void testMapping() credentials("AKIAxxxaccess", "iXbXxxxsecret") .withKmsKeyId("kmsKey_12")); + // matches prefix exactly -- mapping provides credentials, customer key from extra credentials matching default + assertMapping( + provider, + path("s3://baz/") + .withExtraCredentialCustomerKey("customerKey_10"), + credentials("AKIAxxxaccess", "iXbXxxxsecret") + .withSseCustomerKey("customerKey_10")); + + // matches prefix exactly -- mapping provides credentials, customer key from extra credentials, allowed, different from default + assertMapping( + provider, + path("s3://baz/") + .withExtraCredentialCustomerKey("customerKey_11"), + credentials("AKIAxxxaccess", "iXbXxxxsecret") + .withSseCustomerKey("customerKey_11")); + + // matches prefix exactly -- mapping provides credentials, customer key from extra credentials, not allowed + assertMappingFails( + provider, + path("s3://baz/") + .withExtraCredentialCustomerKey("customerKey_not_allowed"), + "Provided SSE Customer Key is not allowed"); + + // matches prefix exactly -- mapping provides credentials, customer key from extra credentials, all keys are allowed, different from default + assertMapping( + provider, + path("s3://baz_all_customer_keys_allowed/") + .withExtraCredentialCustomerKey("customerKey_777"), + credentials("AKIAxxxaccess", "iXbXxxxsecret") + .withSseCustomerKey("customerKey_777")); + + // matches prefix exactly -- mapping provides credentials, customer key from extra credentials, allowed, no default key + assertMapping( + provider, + path("s3://baz_no_customer_default_key/") + .withExtraCredentialCustomerKey("customerKey_12"), + credentials("AKIAxxxaccess", "iXbXxxxsecret") + .withSseCustomerKey("customerKey_12")); + // no role selected and mapping has no default role assertMappingFails( provider, @@ -320,6 +361,8 @@ public void testMappingWithoutRoleCredentialsFallbackShouldFail() Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty(), + Optional.empty(), Optional.empty())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("must either allow useClusterDefault role or provide role and/or credentials"); @@ -343,6 +386,8 @@ public void testMappingWithRoleAndFallbackShouldFail() Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty(), + Optional.empty(), useClusterDefault, Optional.empty(), Optional.empty())) @@ -368,6 +413,8 @@ public void testMappingWithEncryptionKeysAndFallbackShouldFail() Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty(), + Optional.empty(), useClusterDefault, Optional.empty(), Optional.empty())) @@ -375,6 +422,60 @@ public void testMappingWithEncryptionKeysAndFallbackShouldFail() .hasMessage("KMS key ID cannot be provided together with useClusterDefault"); } + @Test + public void testMappingWithSseCustomerKeyAndFallbackShouldFail() + { + Optional useClusterDefault = Optional.of(true); + Optional sseCustomerKey = Optional.of("CLIENT_S3CRT_CUSTOMER_KEY"); + + assertThatThrownBy(() -> + new S3SecurityMapping( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + sseCustomerKey, + Optional.empty(), + Optional.empty(), + Optional.empty(), + useClusterDefault, + Optional.empty(), + Optional.empty())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("SSE Customer key cannot be provided together with useClusterDefault"); + } + + @Test + public void testMappingWithSseCustomerAndKMSKeysShouldFail() + { + Optional kmsKeyId = Optional.of("CLIENT_S3CRT_KEY_ID"); + Optional sseCustomerKey = Optional.of("CLIENT_S3CRT_CUSTOMER_KEY"); + + assertThatThrownBy(() -> + new S3SecurityMapping( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.of("arn:aws:iam::123456789101:role/allow_path"), + Optional.empty(), + Optional.empty(), + kmsKeyId, + Optional.empty(), + sseCustomerKey, + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("SSE Customer key cannot be provided together with KMS key ID"); + } + @Test public void testMappingWithRoleSessionNameWithoutIamRoleShouldFail() { @@ -394,6 +495,8 @@ public void testMappingWithRoleSessionNameWithoutIamRoleShouldFail() Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty(), + Optional.empty(), Optional.empty())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("iamRole must be provided when roleSessionName is provided"); @@ -414,6 +517,7 @@ private static void assertMapping(S3SecurityMappingProvider provider, MappingSel assertThat(actual.iamRole()).isEqualTo(expected.iamRole()); assertThat(actual.roleSessionName()).isEqualTo(expected.roleSessionName()); assertThat(actual.kmsKeyId()).isEqualTo(expected.kmsKeyId()); + assertThat(actual.sseCustomerKey()).isEqualTo(expected.sseCustomerKey()); assertThat(actual.endpoint()).isEqualTo(expected.endpoint()); assertThat(actual.region()).isEqualTo(expected.region()); }); @@ -440,7 +544,7 @@ public static MappingSelector empty() public static MappingSelector path(String location) { - return new MappingSelector(DEFAULT_USER, ImmutableSet.of(), Location.of(location), Optional.empty(), Optional.empty()); + return new MappingSelector(DEFAULT_USER, ImmutableSet.of(), Location.of(location), Optional.empty(), Optional.empty(), Optional.empty()); } private final String user; @@ -448,14 +552,16 @@ public static MappingSelector path(String location) private final Location location; private final Optional extraCredentialIamRole; private final Optional extraCredentialKmsKeyId; + private final Optional extraCredentialCustomerKey; - private MappingSelector(String user, Set groups, Location location, Optional extraCredentialIamRole, Optional extraCredentialKmsKeyId) + private MappingSelector(String user, Set groups, Location location, Optional extraCredentialIamRole, Optional extraCredentialKmsKeyId, Optional extraCredentialCustomerKey) { this.user = requireNonNull(user, "user is null"); this.groups = ImmutableSet.copyOf(requireNonNull(groups, "groups is null")); this.location = requireNonNull(location, "location is null"); this.extraCredentialIamRole = requireNonNull(extraCredentialIamRole, "extraCredentialIamRole is null"); this.extraCredentialKmsKeyId = requireNonNull(extraCredentialKmsKeyId, "extraCredentialKmsKeyId is null"); + this.extraCredentialCustomerKey = requireNonNull(extraCredentialCustomerKey, "extraCredentialCustomerKey is null"); } public Location location() @@ -465,22 +571,27 @@ public Location location() public MappingSelector withExtraCredentialIamRole(String role) { - return new MappingSelector(user, groups, location, Optional.of(role), extraCredentialKmsKeyId); + return new MappingSelector(user, groups, location, Optional.of(role), extraCredentialKmsKeyId, extraCredentialCustomerKey); } public MappingSelector withExtraCredentialKmsKeyId(String kmsKeyId) { - return new MappingSelector(user, groups, location, extraCredentialIamRole, Optional.of(kmsKeyId)); + return new MappingSelector(user, groups, location, extraCredentialIamRole, Optional.of(kmsKeyId), Optional.empty()); + } + + public MappingSelector withExtraCredentialCustomerKey(String customerKey) + { + return new MappingSelector(user, groups, location, extraCredentialIamRole, Optional.empty(), Optional.of(customerKey)); } public MappingSelector withUser(String user) { - return new MappingSelector(user, groups, location, extraCredentialIamRole, extraCredentialKmsKeyId); + return new MappingSelector(user, groups, location, extraCredentialIamRole, extraCredentialKmsKeyId, extraCredentialCustomerKey); } public MappingSelector withGroups(String... groups) { - return new MappingSelector(user, ImmutableSet.copyOf(groups), location, extraCredentialIamRole, extraCredentialKmsKeyId); + return new MappingSelector(user, ImmutableSet.copyOf(groups), location, extraCredentialIamRole, extraCredentialKmsKeyId, extraCredentialCustomerKey); } public ConnectorIdentity identity() @@ -488,6 +599,7 @@ public ConnectorIdentity identity() Map extraCredentials = new HashMap<>(); extraCredentialIamRole.ifPresent(role -> extraCredentials.put(IAM_ROLE_CREDENTIAL_NAME, role)); extraCredentialKmsKeyId.ifPresent(kmsKeyId -> extraCredentials.put(KMS_KEY_ID_CREDENTIAL_NAME, kmsKeyId)); + extraCredentialCustomerKey.ifPresent(customerKey -> extraCredentials.put(CUSTOMER_KEY_CREDENTIAL_NAME, customerKey)); return ConnectorIdentity.forUser(user) .withGroups(groups) @@ -507,6 +619,7 @@ public static MappingResult credentials(String accessKey, String secretKey) Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty(), Optional.empty()); } @@ -519,6 +632,7 @@ public static MappingResult iamRole(String role) Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty(), Optional.empty()); } @@ -527,6 +641,7 @@ public static MappingResult iamRole(String role) private final Optional iamRole; private final Optional roleSessionName; private final Optional kmsKeyId; + private final Optional sseCustomerKey; private final Optional endpoint; private final Optional region; @@ -536,6 +651,7 @@ private MappingResult( Optional iamRole, Optional roleSessionName, Optional kmsKeyId, + Optional sseCustomerKey, Optional endpoint, Optional region) { @@ -543,6 +659,7 @@ private MappingResult( this.secretKey = requireNonNull(secretKey, "secretKey is null"); this.iamRole = requireNonNull(iamRole, "role is null"); this.kmsKeyId = requireNonNull(kmsKeyId, "kmsKeyId is null"); + this.sseCustomerKey = requireNonNull(sseCustomerKey, "sseCustomerKey is null"); this.endpoint = requireNonNull(endpoint, "endpoint is null"); this.roleSessionName = requireNonNull(roleSessionName, "roleSessionName is null"); this.region = requireNonNull(region, "region is null"); @@ -550,22 +667,27 @@ private MappingResult( public MappingResult withEndpoint(String endpoint) { - return new MappingResult(accessKey, secretKey, iamRole, Optional.empty(), kmsKeyId, Optional.of(endpoint), region); + return new MappingResult(accessKey, secretKey, iamRole, Optional.empty(), kmsKeyId, sseCustomerKey, Optional.of(endpoint), region); } public MappingResult withKmsKeyId(String kmsKeyId) { - return new MappingResult(accessKey, secretKey, iamRole, Optional.empty(), Optional.of(kmsKeyId), endpoint, region); + return new MappingResult(accessKey, secretKey, iamRole, Optional.empty(), Optional.of(kmsKeyId), Optional.empty(), endpoint, region); + } + + public MappingResult withSseCustomerKey(String customerKey) + { + return new MappingResult(accessKey, secretKey, iamRole, Optional.empty(), Optional.empty(), Optional.of(customerKey), endpoint, region); } public MappingResult withRegion(String region) { - return new MappingResult(accessKey, secretKey, iamRole, Optional.empty(), kmsKeyId, endpoint, Optional.of(region)); + return new MappingResult(accessKey, secretKey, iamRole, Optional.empty(), kmsKeyId, sseCustomerKey, endpoint, Optional.of(region)); } public MappingResult withRoleSessionName(String roleSessionName) { - return new MappingResult(accessKey, secretKey, iamRole, Optional.of(roleSessionName), kmsKeyId, Optional.empty(), region); + return new MappingResult(accessKey, secretKey, iamRole, Optional.of(roleSessionName), kmsKeyId, sseCustomerKey, Optional.empty(), region); } public Optional accessKey() @@ -593,6 +715,11 @@ public Optional kmsKeyId() return kmsKeyId; } + public Optional sseCustomerKey() + { + return sseCustomerKey; + } + public Optional endpoint() { return endpoint; diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3SecurityMappingConfig.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3SecurityMappingConfig.java index 0aabecd00745..ec51e87fd5bf 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3SecurityMappingConfig.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3SecurityMappingConfig.java @@ -44,6 +44,7 @@ public void testDefaults() .setConfigUri(null) .setRoleCredentialName(null) .setKmsKeyIdCredentialName(null) + .setSseCustomerKeyCredentialName(null) .setRefreshPeriod(null) .setColonReplacement(null)); } @@ -59,6 +60,7 @@ public void testExplicitPropertyMappingsWithFile() .put("s3.security-mapping.json-pointer", "/data") .put("s3.security-mapping.iam-role-credential-name", "iam-role-credential-name") .put("s3.security-mapping.kms-key-id-credential-name", "kms-key-id-credential-name") + .put("s3.security-mapping.sse-customer-key-credential-name", "sse-customer-key-credential-name") .put("s3.security-mapping.refresh-period", "13s") .put("s3.security-mapping.colon-replacement", "#") .buildOrThrow(); @@ -71,6 +73,7 @@ public void testExplicitPropertyMappingsWithFile() assertThat(config.getJsonPointer()).isEqualTo("/data"); assertThat(config.getRoleCredentialName()).contains("iam-role-credential-name"); assertThat(config.getKmsKeyIdCredentialName()).contains("kms-key-id-credential-name"); + assertThat(config.getSseCustomerKeyCredentialName()).contains("sse-customer-key-credential-name"); assertThat(config.getRefreshPeriod()).contains(new Duration(13, SECONDS)); assertThat(config.getColonReplacement()).contains("#"); } @@ -83,6 +86,7 @@ public void testExplicitPropertyMappingsWithUrl() .put("s3.security-mapping.json-pointer", "/data") .put("s3.security-mapping.iam-role-credential-name", "iam-role-credential-name") .put("s3.security-mapping.kms-key-id-credential-name", "kms-key-id-credential-name") + .put("s3.security-mapping.sse-customer-key-credential-name", "sse-customer-key-credential-name") .put("s3.security-mapping.refresh-period", "13s") .put("s3.security-mapping.colon-replacement", "#") .buildOrThrow(); @@ -95,6 +99,7 @@ public void testExplicitPropertyMappingsWithUrl() assertThat(config.getJsonPointer()).isEqualTo("/data"); assertThat(config.getRoleCredentialName()).contains("iam-role-credential-name"); assertThat(config.getKmsKeyIdCredentialName()).contains("kms-key-id-credential-name"); + assertThat(config.getSseCustomerKeyCredentialName()).contains("sse-customer-key-credential-name"); assertThat(config.getRefreshPeriod()).contains(new Duration(13, SECONDS)); assertThat(config.getColonReplacement()).contains("#"); } diff --git a/lib/trino-filesystem-s3/src/test/resources/io/trino/filesystem/s3/security-mapping.json b/lib/trino-filesystem-s3/src/test/resources/io/trino/filesystem/s3/security-mapping.json index a9ddf6e5638d..44e0d58d4a59 100644 --- a/lib/trino-filesystem-s3/src/test/resources/io/trino/filesystem/s3/security-mapping.json +++ b/lib/trino-filesystem-s3/src/test/resources/io/trino/filesystem/s3/security-mapping.json @@ -40,6 +40,26 @@ "secretKey": "iXbXxxxsecret", "allowedKmsKeyIds": ["kmsKey_11", "kmsKey_12"] }, + { + "prefix": "s3://baz/", + "accessKey": "AKIAxxxaccess", + "secretKey": "iXbXxxxsecret", + "sseCustomerKey": "customerKey_10", + "allowedSseCustomerKeys": ["customerKey_11"] + }, + { + "prefix": "s3://baz_all_customer_keys_allowed/", + "accessKey": "AKIAxxxaccess", + "secretKey": "iXbXxxxsecret", + "sseCustomerKey": "customerKey_10", + "allowedSseCustomerKeys": ["*"] + }, + { + "prefix": "s3://baz_no_customer_default_key/", + "accessKey": "AKIAxxxaccess", + "secretKey": "iXbXxxxsecret", + "allowedSseCustomerKeys": ["customerKey_11", "customerKey_12"] + }, { "user": "alice", "iamRole": "alice_role" diff --git a/lib/trino-filesystem/pom.xml b/lib/trino-filesystem/pom.xml index 91ec951a6488..21a156e8b538 100644 --- a/lib/trino-filesystem/pom.xml +++ b/lib/trino-filesystem/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-geospatial-toolkit/pom.xml b/lib/trino-geospatial-toolkit/pom.xml index d3a4dacea661..645db171e803 100644 --- a/lib/trino-geospatial-toolkit/pom.xml +++ b/lib/trino-geospatial-toolkit/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index da3817d881ec..ce397e67bb26 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/s3/AwsSdkClientCoreStats.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/s3/AwsSdkClientCoreStats.java index 88c27e50ca34..af139c41d922 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/s3/AwsSdkClientCoreStats.java +++ b/lib/trino-hdfs/src/main/java/io/trino/hdfs/s3/AwsSdkClientCoreStats.java @@ -44,6 +44,7 @@ public final class AwsSdkClientCoreStats { private final CounterStat awsRequestCount = new CounterStat(); private final CounterStat awsRetryCount = new CounterStat(); + private final CounterStat awsHttpClientRetryCount = new CounterStat(); private final CounterStat awsThrottleExceptions = new CounterStat(); private final TimeStat awsRequestTime = new TimeStat(MILLISECONDS); private final TimeStat awsClientExecuteTime = new TimeStat(MILLISECONDS); @@ -66,6 +67,13 @@ public CounterStat getAwsRetryCount() return awsRetryCount; } + @Managed + @Nested + public CounterStat getAwsHttpClientRetryCount() + { + return awsHttpClientRetryCount; + } + @Managed @Nested public CounterStat getAwsThrottleExceptions() @@ -134,12 +142,16 @@ public void collectMetrics(Request request, Response response) Number requestCounts = timingInfo.getCounter(RequestCount.name()); if (requestCounts != null) { - stats.awsRequestCount.update(requestCounts.longValue()); + long count = requestCounts.longValue(); + stats.awsRequestCount.update(count); + if (count > 1) { + stats.awsRetryCount.update(count - 1); + } } - Number retryCounts = timingInfo.getCounter(HttpClientRetryCount.name()); - if (retryCounts != null) { - stats.awsRetryCount.update(retryCounts.longValue()); + Number httpClientRetryCounts = timingInfo.getCounter(HttpClientRetryCount.name()); + if (httpClientRetryCounts != null) { + stats.awsHttpClientRetryCount.update(httpClientRetryCounts.longValue()); } Number throttleExceptions = timingInfo.getCounter(ThrottleException.name()); 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 413836858627..69b5e96bd53f 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 @@ -15,7 +15,7 @@ import org.apache.hadoop.conf.Configuration; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; /** * Tests file system operations on AWS S3 storage. @@ -31,8 +31,8 @@ public class TestTrinoS3FileSystemAwsS3 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"); + bucketName = requireEnv("S3_BUCKET"); + s3Endpoint = requireEnv("S3_BUCKET_ENDPOINT"); } @Override diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index 5b202a849289..44bf90e72f63 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index 92175857a135..df6407fdf319 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index db57396b9f95..e598ec5a2220 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-metastore/pom.xml b/lib/trino-metastore/pom.xml index 65411b9cbfef..a4415f39af65 100644 --- a/lib/trino-metastore/pom.xml +++ b/lib/trino-metastore/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -33,11 +33,31 @@ guava + + com.google.inject + guice + + + + io.airlift + concurrent + + + + io.airlift + configuration + + io.airlift slice + + io.airlift + units + + io.opentelemetry opentelemetry-api @@ -53,11 +73,31 @@ opentelemetry-semconv + + io.trino + trino-cache + + io.trino trino-spi + + jakarta.annotation + jakarta.annotation-api + + + + jakarta.validation + jakarta.validation-api + + + + org.weakref + jmxutils + + io.airlift junit-extensions diff --git a/lib/trino-metastore/src/main/java/io/trino/metastore/HiveMetastore.java b/lib/trino-metastore/src/main/java/io/trino/metastore/HiveMetastore.java index 8d06dadf5d6b..ff2f44ee0946 100644 --- a/lib/trino-metastore/src/main/java/io/trino/metastore/HiveMetastore.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/HiveMetastore.java @@ -13,6 +13,7 @@ */ package io.trino.metastore; +import com.google.common.collect.ImmutableSet; import io.trino.metastore.HivePrivilegeInfo.HivePrivilege; import io.trino.spi.TrinoException; import io.trino.spi.connector.SchemaTableName; @@ -67,6 +68,11 @@ default boolean useSparkTableStatistics() List getTables(String databaseName); + /** + * @param parameterValues is using ImmutableSet to mark that this api does not support filtering by null parameter value. + */ + List getTableNamesWithParameters(String databaseName, String parameterKey, ImmutableSet parameterValues); + void createDatabase(Database database); void dropDatabase(String databaseName, boolean deleteData); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreFactory.java b/lib/trino-metastore/src/main/java/io/trino/metastore/HiveMetastoreFactory.java similarity index 95% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreFactory.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/HiveMetastoreFactory.java index cab15145c2df..5b0564c8ffe0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreFactory.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/HiveMetastoreFactory.java @@ -11,9 +11,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore; +package io.trino.metastore; -import io.trino.metastore.HiveMetastore; import io.trino.spi.security.ConnectorIdentity; import java.util.Optional; diff --git a/lib/trino-metastore/src/main/java/io/trino/metastore/Partition.java b/lib/trino-metastore/src/main/java/io/trino/metastore/Partition.java index ed3f5dddac88..f24395ee56ff 100644 --- a/lib/trino-metastore/src/main/java/io/trino/metastore/Partition.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/Partition.java @@ -21,7 +21,6 @@ import com.google.errorprone.annotations.Immutable; import io.trino.spi.connector.SchemaTableName; -import java.util.HexFormat; import java.util.List; import java.util.Map; import java.util.Objects; @@ -221,67 +220,4 @@ public Partition build() return new Partition(databaseName, tableName, values, storageBuilder.build(), columns, parameters); } } - - public static List toPartitionValues(String partitionName) - { - // mimics Warehouse.makeValsFromName - ImmutableList.Builder resultBuilder = ImmutableList.builder(); - int start = 0; - while (true) { - while (start < partitionName.length() && partitionName.charAt(start) != '=') { - start++; - } - start++; - int end = start; - while (end < partitionName.length() && partitionName.charAt(end) != '/') { - end++; - } - if (start > partitionName.length()) { - break; - } - resultBuilder.add(unescapePathName(partitionName.substring(start, end))); - start = end + 1; - } - return resultBuilder.build(); - } - - // copy of org.apache.hadoop.hive.common.FileUtils#unescapePathName - @SuppressWarnings("NumericCastThatLosesPrecision") - public static String unescapePathName(String path) - { - // fast path, no escaped characters and therefore no copying necessary - int escapedAtIndex = path.indexOf('%'); - if (escapedAtIndex < 0 || escapedAtIndex + 2 >= path.length()) { - return path; - } - - // slow path, unescape into a new string copy - StringBuilder sb = new StringBuilder(); - int fromIndex = 0; - while (escapedAtIndex >= 0 && escapedAtIndex + 2 < path.length()) { - // preceding sequence without escaped characters - if (escapedAtIndex > fromIndex) { - sb.append(path, fromIndex, escapedAtIndex); - } - // try to parse the to digits after the percent sign as hex - try { - int code = HexFormat.fromHexDigits(path, escapedAtIndex + 1, escapedAtIndex + 3); - sb.append((char) code); - // advance past the percent sign and both hex digits - fromIndex = escapedAtIndex + 3; - } - catch (NumberFormatException e) { - // invalid escape sequence, only advance past the percent sign - sb.append('%'); - fromIndex = escapedAtIndex + 1; - } - // find next escaped character - escapedAtIndex = path.indexOf('%', fromIndex); - } - // trailing sequence without escaped characters - if (fromIndex < path.length()) { - sb.append(path, fromIndex, path.length()); - } - return sb.toString(); - } } diff --git a/lib/trino-metastore/src/main/java/io/trino/metastore/PartitionWithStatistics.java b/lib/trino-metastore/src/main/java/io/trino/metastore/PartitionWithStatistics.java index 77c136c0e6f1..fa695bb5d410 100644 --- a/lib/trino-metastore/src/main/java/io/trino/metastore/PartitionWithStatistics.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/PartitionWithStatistics.java @@ -14,7 +14,7 @@ package io.trino.metastore; import static com.google.common.base.Preconditions.checkArgument; -import static io.trino.metastore.Partition.toPartitionValues; +import static io.trino.metastore.Partitions.toPartitionValues; import static java.util.Objects.requireNonNull; public class PartitionWithStatistics diff --git a/lib/trino-metastore/src/main/java/io/trino/metastore/Partitions.java b/lib/trino-metastore/src/main/java/io/trino/metastore/Partitions.java new file mode 100644 index 000000000000..f6450f32a50a --- /dev/null +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/Partitions.java @@ -0,0 +1,154 @@ +/* + * 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.metastore; + +import com.google.common.base.CharMatcher; +import com.google.common.collect.ImmutableList; + +import java.util.HexFormat; +import java.util.List; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.util.Locale.ENGLISH; + +public final class Partitions +{ + public static final String HIVE_DEFAULT_DYNAMIC_PARTITION = "__HIVE_DEFAULT_PARTITION__"; + + private static final HexFormat HEX_UPPER_FORMAT = HexFormat.of().withUpperCase(); + + private static final CharMatcher PATH_CHAR_TO_ESCAPE = CharMatcher.inRange((char) 0, (char) 31) + .or(CharMatcher.anyOf("\"#%'*/:=?\\\u007F{[]^")) + .precomputed(); + + private Partitions() {} + + public static List toPartitionValues(String partitionName) + { + // mimics Warehouse.makeValsFromName + ImmutableList.Builder resultBuilder = ImmutableList.builder(); + int start = 0; + while (true) { + while (start < partitionName.length() && partitionName.charAt(start) != '=') { + start++; + } + start++; + int end = start; + while (end < partitionName.length() && partitionName.charAt(end) != '/') { + end++; + } + if (start > partitionName.length()) { + break; + } + resultBuilder.add(unescapePathName(partitionName.substring(start, end))); + start = end + 1; + } + return resultBuilder.build(); + } + + // copy of org.apache.hadoop.hive.common.FileUtils#unescapePathName + @SuppressWarnings("NumericCastThatLosesPrecision") + public static String unescapePathName(String path) + { + // fast path, no escaped characters and therefore no copying necessary + int escapedAtIndex = path.indexOf('%'); + if (escapedAtIndex < 0 || escapedAtIndex + 2 >= path.length()) { + return path; + } + + // slow path, unescape into a new string copy + StringBuilder sb = new StringBuilder(); + int fromIndex = 0; + while (escapedAtIndex >= 0 && escapedAtIndex + 2 < path.length()) { + // preceding sequence without escaped characters + if (escapedAtIndex > fromIndex) { + sb.append(path, fromIndex, escapedAtIndex); + } + // try to parse the to digits after the percent sign as hex + try { + int code = HexFormat.fromHexDigits(path, escapedAtIndex + 1, escapedAtIndex + 3); + sb.append((char) code); + // advance past the percent sign and both hex digits + fromIndex = escapedAtIndex + 3; + } + catch (NumberFormatException e) { + // invalid escape sequence, only advance past the percent sign + sb.append('%'); + fromIndex = escapedAtIndex + 1; + } + // find next escaped character + escapedAtIndex = path.indexOf('%', fromIndex); + } + // trailing sequence without escaped characters + if (fromIndex < path.length()) { + sb.append(path, fromIndex, path.length()); + } + return sb.toString(); + } + + // copy of org.apache.hadoop.hive.common.FileUtils#escapePathName + public static String escapePathName(String path) + { + if (isNullOrEmpty(path)) { + return HIVE_DEFAULT_DYNAMIC_PARTITION; + } + + // Fast-path detection, no escaping and therefore no copying necessary + int escapeAtIndex = PATH_CHAR_TO_ESCAPE.indexIn(path); + if (escapeAtIndex < 0) { + return path; + } + + // slow path, escape beyond the first required escape character into a new string + StringBuilder sb = new StringBuilder(); + int fromIndex = 0; + while (escapeAtIndex >= 0 && escapeAtIndex < path.length()) { + // preceding characters without escaping needed + if (escapeAtIndex > fromIndex) { + sb.append(path, fromIndex, escapeAtIndex); + } + // escape single character + char c = path.charAt(escapeAtIndex); + sb.append('%').append(HEX_UPPER_FORMAT.toHighHexDigit(c)).append(HEX_UPPER_FORMAT.toLowHexDigit(c)); + // find next character to escape + fromIndex = escapeAtIndex + 1; + if (fromIndex < path.length()) { + escapeAtIndex = PATH_CHAR_TO_ESCAPE.indexIn(path, fromIndex); + } + else { + escapeAtIndex = -1; + } + } + // trailing characters without escaping needed + if (fromIndex < path.length()) { + sb.append(path, fromIndex, path.length()); + } + return sb.toString(); + } + + // copy of org.apache.hadoop.hive.common.FileUtils#makePartName + public static String makePartName(List columns, List values) + { + StringBuilder name = new StringBuilder(); + for (int i = 0; i < columns.size(); i++) { + if (i > 0) { + name.append('/'); + } + name.append(escapePathName(columns.get(i).toLowerCase(ENGLISH))); + name.append('='); + name.append(escapePathName(values.get(i))); + } + return name.toString(); + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/RawHiveMetastoreFactory.java b/lib/trino-metastore/src/main/java/io/trino/metastore/RawHiveMetastoreFactory.java similarity index 96% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/RawHiveMetastoreFactory.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/RawHiveMetastoreFactory.java index a9d7b5b793b0..573bb4b23efd 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/RawHiveMetastoreFactory.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/RawHiveMetastoreFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore; +package io.trino.metastore; import com.google.inject.BindingAnnotation; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SchemaAlreadyExistsException.java b/lib/trino-metastore/src/main/java/io/trino/metastore/SchemaAlreadyExistsException.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/SchemaAlreadyExistsException.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/SchemaAlreadyExistsException.java index 8bff8da39a12..df46c9e8705a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SchemaAlreadyExistsException.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/SchemaAlreadyExistsException.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive; +package io.trino.metastore; import io.trino.spi.TrinoException; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TableAlreadyExistsException.java b/lib/trino-metastore/src/main/java/io/trino/metastore/TableAlreadyExistsException.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/TableAlreadyExistsException.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/TableAlreadyExistsException.java index ff0e9fdae2bb..83f293809b76 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TableAlreadyExistsException.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/TableAlreadyExistsException.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive; +package io.trino.metastore; import io.trino.spi.TrinoException; import io.trino.spi.connector.SchemaTableName; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/CachingHiveMetastore.java similarity index 96% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/cache/CachingHiveMetastore.java index bcee3aa656d5..0c22b77d39f9 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/CachingHiveMetastore.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.cache; +package io.trino.metastore.cache; import com.google.common.cache.Cache; import com.google.common.cache.CacheLoader; @@ -44,9 +44,6 @@ import io.trino.metastore.StatisticsUpdateMode; import io.trino.metastore.Table; import io.trino.metastore.TableInfo; -import io.trino.plugin.hive.metastore.HivePartitionName; -import io.trino.plugin.hive.metastore.HiveTableName; -import io.trino.plugin.hive.metastore.PartitionFilter; import io.trino.spi.TrinoException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.function.LanguageFunction; @@ -85,13 +82,13 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static io.trino.cache.CacheUtils.invalidateAllIf; import static io.trino.cache.CacheUtils.uncheckedCacheGet; -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.PartitionFilter.partitionFilter; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.ObjectType.OTHER; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.ObjectType.PARTITION; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.ObjectType.STATS; -import static io.trino.plugin.hive.util.HiveUtil.makePartName; +import static io.trino.metastore.Partitions.makePartName; +import static io.trino.metastore.cache.CachingHiveMetastore.ObjectType.OTHER; +import static io.trino.metastore.cache.CachingHiveMetastore.ObjectType.PARTITION; +import static io.trino.metastore.cache.CachingHiveMetastore.ObjectType.STATS; +import static io.trino.metastore.cache.HivePartitionName.hivePartitionName; +import static io.trino.metastore.cache.HiveTableName.hiveTableName; +import static io.trino.metastore.cache.PartitionFilter.partitionFilter; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -123,6 +120,7 @@ public enum ObjectType private final LoadingCache> tableCache; private final LoadingCache> tablesCacheNew; private final Cache>> tableColumnStatisticsCache; + private final LoadingCache> tableNamesWithParametersCache; private final Cache>> partitionStatisticsCache; private final Cache>> partitionCache; private final LoadingCache>> partitionFilterCache; @@ -206,6 +204,7 @@ private CachingHiveMetastore( tablesCacheNew = cacheFactory.buildCache(this::loadTablesNew); tableColumnStatisticsCache = statsCacheFactory.buildCache(this::refreshTableColumnStatistics); tableCache = cacheFactory.buildCache(this::loadTable); + tableNamesWithParametersCache = cacheFactory.buildCache(this::loadTablesMatchingParameter); tablePrivilegesCache = cacheFactory.buildCache(key -> loadTablePrivileges(key.database(), key.table(), key.owner(), key.principal())); rolesCache = cacheFactory.buildCache(_ -> loadRoles()); roleGrantsCache = cacheFactory.buildCache(this::loadRoleGrants); @@ -223,6 +222,7 @@ public void flushCache() tablesCacheNew.invalidateAll(); databaseCache.invalidateAll(); tableCache.invalidateAll(); + tableNamesWithParametersCache.invalidateAll(); partitionCache.invalidateAll(); partitionFilterCache.invalidateAll(); tablePrivilegesCache.invalidateAll(); @@ -565,6 +565,18 @@ private List loadTablesNew(String databaseName) return delegate.getTables(databaseName); } + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, ImmutableSet parameterValues) + { + TablesWithParameterCacheKey key = new TablesWithParameterCacheKey(databaseName, parameterKey, parameterValues); + return get(tableNamesWithParametersCache, key); + } + + private List loadTablesMatchingParameter(TablesWithParameterCacheKey key) + { + return delegate.getTableNamesWithParameters(key.databaseName(), key.parameterKey(), key.parameterValues()); + } + @Override public void createDatabase(Database database) { @@ -733,6 +745,7 @@ public void invalidateTable(String databaseName, String tableName) HiveTableName hiveTableName = new HiveTableName(databaseName, tableName); tableCache.invalidate(hiveTableName); tablesCacheNew.invalidate(databaseName); + tableNamesWithParametersCache.invalidateAll(); invalidateAllIf(tablePrivilegesCache, userTableKey -> userTableKey.matches(databaseName, tableName)); tableColumnStatisticsCache.invalidate(hiveTableName); invalidatePartitionCache(databaseName, tableName); @@ -1153,6 +1166,16 @@ private static Cache> buildBulkCache( return cacheBuilder.build(); } + record TablesWithParameterCacheKey(String databaseName, String parameterKey, ImmutableSet parameterValues) + { + TablesWithParameterCacheKey + { + requireNonNull(databaseName, "databaseName is null"); + requireNonNull(parameterKey, "parameterKey is null"); + requireNonNull(parameterValues, "parameterValues is null"); + } + } + record UserTableKey(Optional principal, String database, String table, Optional owner) { UserTableKey @@ -1201,6 +1224,13 @@ public CacheStatsMBean getTableNamesStats() return new CacheStatsMBean(tablesCacheNew); } + @Managed + @Nested + public CacheStatsMBean getTableWithParameterStats() + { + return new CacheStatsMBean(tableNamesWithParametersCache); + } + @Managed @Nested public CacheStatsMBean getTableColumnStatisticsStats() @@ -1275,6 +1305,11 @@ LoadingCache> getTableCache() return tableCache; } + LoadingCache> getTableNamesWithParametersCache() + { + return tableNamesWithParametersCache; + } + public LoadingCache> getTablesCacheNew() { return tablesCacheNew; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/CachingHiveMetastoreConfig.java similarity index 99% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/cache/CachingHiveMetastoreConfig.java index 2f97cbc454ed..b69c62d6b131 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/CachingHiveMetastoreConfig.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.cache; +package io.trino.metastore.cache; import io.airlift.configuration.Config; import io.airlift.units.Duration; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePartitionName.java b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/HivePartitionName.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePartitionName.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/cache/HivePartitionName.java index 9c8a9c5ce3cd..dab9f21cfa00 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePartitionName.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/HivePartitionName.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore; +package io.trino.metastore.cache; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -23,7 +23,7 @@ import java.util.Optional; import static com.google.common.base.MoreObjects.toStringHelper; -import static io.trino.metastore.Partition.toPartitionValues; +import static io.trino.metastore.Partitions.toPartitionValues; import static java.util.Objects.requireNonNull; @Immutable diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveTableName.java b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/HiveTableName.java similarity index 98% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveTableName.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/cache/HiveTableName.java index dde5609a0bac..85281946e1a8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveTableName.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/HiveTableName.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore; +package io.trino.metastore.cache; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/ImpersonationCachingConfig.java b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/ImpersonationCachingConfig.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/ImpersonationCachingConfig.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/cache/ImpersonationCachingConfig.java index 15eb31787d94..29268b002c1d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/ImpersonationCachingConfig.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/ImpersonationCachingConfig.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.cache; +package io.trino.metastore.cache; import io.airlift.configuration.Config; import io.airlift.units.Duration; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/PartitionFilter.java b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/PartitionFilter.java similarity index 96% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/PartitionFilter.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/cache/PartitionFilter.java index 883b9a1641c2..73695573927c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/PartitionFilter.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/PartitionFilter.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore; +package io.trino.metastore.cache; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -23,7 +23,7 @@ import java.util.Objects; import static com.google.common.base.MoreObjects.toStringHelper; -import static io.trino.plugin.hive.metastore.HiveTableName.hiveTableName; +import static io.trino.metastore.cache.HiveTableName.hiveTableName; import static java.util.Objects.requireNonNull; @Immutable diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/ReentrantBoundedExecutor.java b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/ReentrantBoundedExecutor.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/ReentrantBoundedExecutor.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/cache/ReentrantBoundedExecutor.java index 6005d3c77d6d..0480695a8f00 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/ReentrantBoundedExecutor.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/ReentrantBoundedExecutor.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.cache; +package io.trino.metastore.cache; import io.airlift.concurrent.BoundedExecutor; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/SharedHiveMetastoreCache.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java rename to lib/trino-metastore/src/main/java/io/trino/metastore/cache/SharedHiveMetastoreCache.java index b8c50af89a55..68adfd506063 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/cache/SharedHiveMetastoreCache.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.cache; +package io.trino.metastore.cache; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -24,8 +24,8 @@ import com.google.inject.Inject; import io.airlift.units.Duration; import io.trino.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.ObjectType; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.cache.CachingHiveMetastore.ObjectType; import io.trino.spi.NodeManager; import io.trino.spi.TrinoException; import io.trino.spi.catalog.CatalogName; @@ -261,6 +261,13 @@ public AggregateCacheStatsMBean getTablesStats() return new AggregateCacheStatsMBean(CachingHiveMetastore::getTablesCacheNew); } + @Managed + @Nested + public AggregateCacheStatsMBean getTableWithParameterStats() + { + return new AggregateCacheStatsMBean(CachingHiveMetastore::getTableNamesWithParametersCache); + } + @Managed @Nested public AggregateCacheStatsMBean getTableColumnStatisticsCache() diff --git a/lib/trino-metastore/src/main/java/io/trino/metastore/tracing/TracingHiveMetastore.java b/lib/trino-metastore/src/main/java/io/trino/metastore/tracing/TracingHiveMetastore.java index e8c6f56631ca..4549a69af5cc 100644 --- a/lib/trino-metastore/src/main/java/io/trino/metastore/tracing/TracingHiveMetastore.java +++ b/lib/trino-metastore/src/main/java/io/trino/metastore/tracing/TracingHiveMetastore.java @@ -13,6 +13,7 @@ */ package io.trino.metastore.tracing; +import com.google.common.collect.ImmutableSet; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; import io.trino.metastore.AcidOperation; @@ -164,6 +165,20 @@ public List getTables(String databaseName) }); } + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, ImmutableSet parameterValues) + { + Span span = tracer.spanBuilder("HiveMetastore.getTableNamesWithParameters") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, parameterKey) + .startSpan(); + return withTracing(span, () -> { + List tables = delegate.getTableNamesWithParameters(databaseName, parameterKey, parameterValues); + span.setAttribute(TABLE_RESPONSE_COUNT, tables.size()); + return tables; + }); + } + @Override public void createDatabase(Database database) { diff --git a/lib/trino-metastore/src/test/java/io/trino/metastore/TestPartitions.java b/lib/trino-metastore/src/test/java/io/trino/metastore/TestPartitions.java new file mode 100644 index 000000000000..c19a3afc5cd8 --- /dev/null +++ b/lib/trino-metastore/src/test/java/io/trino/metastore/TestPartitions.java @@ -0,0 +1,116 @@ +/* + * 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.metastore; + +import org.apache.hadoop.hive.common.FileUtils; +import org.apache.hadoop.hive.metastore.Warehouse; +import org.apache.hadoop.hive.metastore.api.MetaException; +import org.junit.jupiter.api.Test; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; + +import static io.trino.metastore.Partitions.toPartitionValues; +import static org.assertj.core.api.Assertions.assertThat; + +public class TestPartitions +{ + @Test + public void testToPartitionValues() + throws MetaException + { + assertToPartitionValues("ds=2015-12-30/event_type=QueryCompletion"); + assertToPartitionValues("ds=2015-12-30"); + assertToPartitionValues("a=1/b=2/c=3"); + assertToPartitionValues("a=1"); + assertToPartitionValues("pk=!@%23$%25%5E&%2A()%2F%3D"); + assertToPartitionValues("pk=__HIVE_DEFAULT_PARTITION__"); + } + + private static void assertToPartitionValues(String partitionName) + throws MetaException + { + List actual = toPartitionValues(partitionName); + AbstractList expected = new ArrayList<>(); + actual.forEach(s -> expected.add(null)); + Warehouse.makeValsFromName(partitionName, expected); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testUnescapePathName() + { + assertUnescapePathName("", ""); + assertUnescapePathName("x", "x"); + assertUnescapePathName("abc", "abc"); + assertUnescapePathName("abc%", "abc%"); + assertUnescapePathName("%", "%"); + assertUnescapePathName("%41", "A"); + assertUnescapePathName("%41%x", "A%x"); + assertUnescapePathName("%41%xxZ", "A%xxZ"); + assertUnescapePathName("%41%%Z", "A%%Z"); + assertUnescapePathName("%41%25%25Z", "A%%Z"); + assertUnescapePathName("abc%41%42%43", "abcABC"); + assertUnescapePathName("abc%3Axyz", "abc:xyz"); + assertUnescapePathName("abc%3axyz", "abc:xyz"); + assertUnescapePathName("abc%BBxyz", "abc\u00BBxyz"); + } + + private static void assertUnescapePathName(String value, String expected) + { + assertThat(FileUtils.unescapePathName(value)).isEqualTo(expected); + assertThat(Partitions.unescapePathName(value)).isEqualTo(expected); + } + + @Test + public void testEscapePathName() + { + assertEscapePathName(null, "__HIVE_DEFAULT_PARTITION__"); + assertEscapePathName("", "__HIVE_DEFAULT_PARTITION__"); + assertEscapePathName("x", "x"); + assertEscapePathName("abc", "abc"); + assertEscapePathName("%", "%25"); + assertEscapePathName("A", "A"); + assertEscapePathName("A%x", "A%25x"); + assertEscapePathName("A%xxZ", "A%25xxZ"); + assertEscapePathName("A%%Z", "A%25%25Z"); + assertEscapePathName("abcABC", "abcABC"); + assertEscapePathName("abc:xyz", "abc%3Axyz"); + assertEscapePathName("abc\u00BBxyz", "abc\u00BBxyz"); + assertEscapePathName("\u0000\t\b\r\n\u001F", "%00%09%08%0D%0A%1F"); + assertEscapePathName("#%^&*=[]{\\:'\"/?", "%23%25%5E&%2A%3D%5B%5D%7B%5C%3A%27%22%2F%3F"); + assertEscapePathName("~`!@$()-_+}|;,.<>", "~`!@$()-_+}|;,.<>"); + } + + private static void assertEscapePathName(String value, String expected) + { + assertThat(FileUtils.escapePathName(value)).isEqualTo(expected); + assertThat(Partitions.escapePathName(value)).isEqualTo(expected); + } + + @Test + public void testMakePartName() + { + assertMakePartName(List.of("abc"), List.of("xyz"), "abc=xyz"); + assertMakePartName(List.of("abc:qqq"), List.of("xyz/yyy=zzz"), "abc%3Aqqq=xyz%2Fyyy%3Dzzz"); + assertMakePartName(List.of("abc", "def", "xyz"), List.of("qqq", "rrr", "sss"), "abc=qqq/def=rrr/xyz=sss"); + } + + private static void assertMakePartName(List columns, List values, String expected) + { + assertThat(FileUtils.makePartName(columns, values)).isEqualTo(expected); + assertThat(Partitions.makePartName(columns, values)).isEqualTo(expected); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java b/lib/trino-metastore/src/test/java/io/trino/metastore/cache/TestCachingHiveMetastoreConfig.java similarity index 96% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java rename to lib/trino-metastore/src/test/java/io/trino/metastore/cache/TestCachingHiveMetastoreConfig.java index 38d59830b42b..5047c09a1301 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java +++ b/lib/trino-metastore/src/test/java/io/trino/metastore/cache/TestCachingHiveMetastoreConfig.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.cache; +package io.trino.metastore.cache; import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; @@ -22,7 +22,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.trino.plugin.hive.metastore.cache.CachingHiveMetastoreConfig.DEFAULT_STATS_CACHE_TTL; +import static io.trino.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; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestImpersonationCachingConfig.java b/lib/trino-metastore/src/test/java/io/trino/metastore/cache/TestImpersonationCachingConfig.java similarity index 97% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestImpersonationCachingConfig.java rename to lib/trino-metastore/src/test/java/io/trino/metastore/cache/TestImpersonationCachingConfig.java index 63edc9a23e1e..7127c141532d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestImpersonationCachingConfig.java +++ b/lib/trino-metastore/src/test/java/io/trino/metastore/cache/TestImpersonationCachingConfig.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.cache; +package io.trino.metastore.cache; import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestReentrantBoundedExecutor.java b/lib/trino-metastore/src/test/java/io/trino/metastore/cache/TestReentrantBoundedExecutor.java similarity index 97% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestReentrantBoundedExecutor.java rename to lib/trino-metastore/src/test/java/io/trino/metastore/cache/TestReentrantBoundedExecutor.java index 3f4c04ae283c..feadf2e49a18 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestReentrantBoundedExecutor.java +++ b/lib/trino-metastore/src/test/java/io/trino/metastore/cache/TestReentrantBoundedExecutor.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.cache; +package io.trino.metastore.cache; import com.google.common.util.concurrent.SettableFuture; import org.junit.jupiter.api.Test; diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 37b7e911e8ad..c3a4a125421a 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index 3c5924e4d3f7..5a25b76485aa 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/metadata/ParquetMetadata.java b/lib/trino-parquet/src/main/java/io/trino/parquet/metadata/ParquetMetadata.java index 9363add2ca6c..56091d962a08 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/metadata/ParquetMetadata.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/metadata/ParquetMetadata.java @@ -13,32 +13,252 @@ */ package io.trino.parquet.metadata; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.log.Logger; +import io.trino.parquet.ParquetCorruptionException; +import io.trino.parquet.ParquetDataSourceId; +import io.trino.parquet.reader.MetadataReader; +import org.apache.parquet.column.Encoding; +import org.apache.parquet.format.ColumnChunk; +import org.apache.parquet.format.ColumnMetaData; +import org.apache.parquet.format.FileMetaData; +import org.apache.parquet.format.KeyValue; +import org.apache.parquet.format.RowGroup; +import org.apache.parquet.format.SchemaElement; +import org.apache.parquet.hadoop.metadata.ColumnPath; +import org.apache.parquet.hadoop.metadata.CompressionCodecName; +import org.apache.parquet.schema.LogicalTypeAnnotation; +import org.apache.parquet.schema.MessageType; +import org.apache.parquet.schema.PrimitiveType; +import org.apache.parquet.schema.Type; +import org.apache.parquet.schema.Types; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.trino.parquet.ParquetMetadataConverter.convertEncodingStats; +import static io.trino.parquet.ParquetMetadataConverter.getEncoding; +import static io.trino.parquet.ParquetMetadataConverter.getLogicalTypeAnnotation; +import static io.trino.parquet.ParquetMetadataConverter.getPrimitive; +import static io.trino.parquet.ParquetMetadataConverter.toColumnIndexReference; +import static io.trino.parquet.ParquetMetadataConverter.toOffsetIndexReference; +import static io.trino.parquet.ParquetValidationUtils.validateParquet; +import static java.util.Objects.requireNonNull; public class ParquetMetadata { - private final FileMetadata fileMetaData; - private final List blocks; + private static final Logger log = Logger.get(ParquetMetadata.class); + + private final FileMetaData parquetMetadata; + private final ParquetDataSourceId dataSourceId; + private final FileMetadata fileMetadata; + + public ParquetMetadata(FileMetaData parquetMetadata, ParquetDataSourceId dataSourceId) + throws ParquetCorruptionException + { + this.fileMetadata = new FileMetadata( + readMessageType(parquetMetadata, dataSourceId), + keyValueMetaData(parquetMetadata), + parquetMetadata.getCreated_by()); + this.parquetMetadata = parquetMetadata; + this.dataSourceId = requireNonNull(dataSourceId, "dataSourceId is null"); + } + + public FileMetadata getFileMetaData() + { + return fileMetadata; + } - public ParquetMetadata(FileMetadata fileMetaData, List blocks) + @Override + public String toString() { - this.fileMetaData = fileMetaData; - this.blocks = blocks; + return toStringHelper(this) + .add("parquetMetadata", parquetMetadata) + .toString(); } public List getBlocks() + throws ParquetCorruptionException { + return getBlocks(0, Long.MAX_VALUE); + } + + public List getBlocks(long splitStart, long splitLength) + throws ParquetCorruptionException + { + List schema = parquetMetadata.getSchema(); + validateParquet(!schema.isEmpty(), dataSourceId, "Schema is empty"); + + MessageType messageType = readParquetSchema(schema); + List blocks = new ArrayList<>(); + List rowGroups = parquetMetadata.getRow_groups(); + if (rowGroups != null) { + for (RowGroup rowGroup : rowGroups) { + if (rowGroup.isSetFile_offset()) { + long rowGroupStart = rowGroup.getFile_offset(); + boolean splitContainsRowGroup = splitStart <= rowGroupStart && rowGroupStart < splitStart + splitLength; + if (!splitContainsRowGroup) { + continue; + } + } + + List columns = rowGroup.getColumns(); + validateParquet(!columns.isEmpty(), dataSourceId, "No columns in row group: %s", rowGroup); + String filePath = columns.get(0).getFile_path(); + ImmutableList.Builder columnMetadataBuilder = ImmutableList.builderWithExpectedSize(columns.size()); + 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() + .map(value -> value.toLowerCase(Locale.ENGLISH)) + .toArray(String[]::new); + ColumnPath columnPath = ColumnPath.get(path); + PrimitiveType primitiveType = messageType.getType(columnPath.toArray()).asPrimitiveType(); + ColumnChunkMetadata column = ColumnChunkMetadata.get( + columnPath, + primitiveType, + CompressionCodecName.fromParquet(metaData.codec), + convertEncodingStats(metaData.encoding_stats), + readEncodings(metaData.encodings), + MetadataReader.readStats(Optional.ofNullable(parquetMetadata.getCreated_by()), Optional.ofNullable(metaData.statistics), primitiveType), + metaData.data_page_offset, + metaData.dictionary_page_offset, + metaData.num_values, + metaData.total_compressed_size, + metaData.total_uncompressed_size); + column.setColumnIndexReference(toColumnIndexReference(columnChunk)); + column.setOffsetIndexReference(toOffsetIndexReference(columnChunk)); + column.setBloomFilterOffset(metaData.bloom_filter_offset); + columnMetadataBuilder.add(column); + } + blocks.add(new BlockMetadata(rowGroup.getNum_rows(), columnMetadataBuilder.build())); + } + } + return blocks; } - public FileMetadata getFileMetaData() + @VisibleForTesting + public FileMetaData getParquetMetadata() { - return fileMetaData; + return parquetMetadata; } - @Override - public String toString() + private static MessageType readParquetSchema(List schema) + { + Iterator schemaIterator = schema.iterator(); + SchemaElement rootSchema = schemaIterator.next(); + Types.MessageTypeBuilder builder = Types.buildMessage(); + readTypeSchema(builder, schemaIterator, rootSchema.getNum_children()); + return builder.named(rootSchema.name); + } + + private static void readTypeSchema(Types.GroupBuilder builder, Iterator schemaIterator, int typeCount) + { + for (int i = 0; i < typeCount; i++) { + SchemaElement element = schemaIterator.next(); + Types.Builder typeBuilder; + if (element.type == null) { + typeBuilder = builder.group(Type.Repetition.valueOf(element.repetition_type.name())); + readTypeSchema((Types.GroupBuilder) typeBuilder, schemaIterator, element.num_children); + } + else { + Types.PrimitiveBuilder primitiveBuilder = builder.primitive(getPrimitive(element.type), Type.Repetition.valueOf(element.repetition_type.name())); + if (element.isSetType_length()) { + primitiveBuilder.length(element.type_length); + } + if (element.isSetPrecision()) { + primitiveBuilder.precision(element.precision); + } + if (element.isSetScale()) { + primitiveBuilder.scale(element.scale); + } + typeBuilder = primitiveBuilder; + } + + // Reading of element.logicalType and element.converted_type corresponds to parquet-mr's code at + // https://github.com/apache/parquet-mr/blob/apache-parquet-1.12.0/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java#L1568-L1582 + LogicalTypeAnnotation annotationFromLogicalType = null; + if (element.isSetLogicalType()) { + annotationFromLogicalType = getLogicalTypeAnnotation(element.logicalType); + typeBuilder.as(annotationFromLogicalType); + } + if (element.isSetConverted_type()) { + LogicalTypeAnnotation annotationFromConvertedType = getLogicalTypeAnnotation(element.converted_type, element); + if (annotationFromLogicalType != null) { + // Both element.logicalType and element.converted_type set + if (annotationFromLogicalType.toOriginalType() == annotationFromConvertedType.toOriginalType()) { + // element.converted_type matches element.logicalType, even though annotationFromLogicalType may differ from annotationFromConvertedType + // Following parquet-mr behavior, we favor LogicalTypeAnnotation derived from element.logicalType, as potentially containing more information. + } + else { + // Following parquet-mr behavior, issue warning and let converted_type take precedence. + log.warn("Converted type and logical type metadata map to different OriginalType (convertedType: %s, logical type: %s). Using value in converted type.", + element.converted_type, element.logicalType); + // parquet-mr reads only OriginalType from converted_type. We retain full LogicalTypeAnnotation + // 1. for compatibility, as previous Trino reader code would read LogicalTypeAnnotation from element.converted_type and some additional fields. + // 2. so that we override LogicalTypeAnnotation annotation read from element.logicalType in case of mismatch detected. + typeBuilder.as(annotationFromConvertedType); + } + } + else { + // parquet-mr reads only OriginalType from converted_type. We retain full LogicalTypeAnnotation for compatibility, as previous + // Trino reader code would read LogicalTypeAnnotation from element.converted_type and some additional fields. + typeBuilder.as(annotationFromConvertedType); + } + } + + if (element.isSetField_id()) { + typeBuilder.id(element.field_id); + } + typeBuilder.named(element.name.toLowerCase(Locale.ENGLISH)); + } + } + + private static Set readEncodings(List encodings) + { + Set columnEncodings = new HashSet<>(); + for (org.apache.parquet.format.Encoding encoding : encodings) { + columnEncodings.add(getEncoding(encoding)); + } + return Collections.unmodifiableSet(columnEncodings); + } + + private static MessageType readMessageType(FileMetaData parquetMetadata, ParquetDataSourceId dataSourceId) + throws ParquetCorruptionException + { + List schema = parquetMetadata.getSchema(); + validateParquet(!schema.isEmpty(), dataSourceId, "Schema is empty"); + + Iterator schemaIterator = schema.iterator(); + SchemaElement rootSchema = schemaIterator.next(); + Types.MessageTypeBuilder builder = Types.buildMessage(); + readTypeSchema(builder, schemaIterator, rootSchema.getNum_children()); + return builder.named(rootSchema.name); + } + + private static Map keyValueMetaData(FileMetaData parquetMetadata) { - return "ParquetMetaData{" + fileMetaData + ", blocks: " + blocks + "}"; + if (parquetMetadata.getKey_value_metadata() == null) { + return ImmutableMap.of(); + } + return parquetMetadata.getKey_value_metadata() + .stream() + .collect(toImmutableMap(KeyValue::getKey, KeyValue::getValue, (_, second) -> second)); } } 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 6901bb23a4e6..978e915a7a56 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 @@ -27,6 +27,7 @@ import io.trino.parquet.ParquetReaderOptions; import io.trino.parquet.metadata.BlockMetadata; import io.trino.parquet.metadata.ColumnChunkMetadata; +import io.trino.parquet.metadata.ParquetMetadata; import io.trino.parquet.metadata.PrunedBlockMetadata; import io.trino.parquet.reader.RowGroupInfo; import io.trino.spi.predicate.TupleDomain; @@ -183,7 +184,7 @@ public static List getFilteredRowGroups( long splitStart, long splitLength, ParquetDataSource dataSource, - List blocksMetaData, + ParquetMetadata parquetMetadata, List> parquetTupleDomains, List parquetPredicates, Map, ColumnDescriptor> descriptorsByPath, @@ -194,7 +195,7 @@ public static List getFilteredRowGroups( { long fileRowCount = 0; ImmutableList.Builder rowGroupInfoBuilder = ImmutableList.builder(); - for (BlockMetadata block : blocksMetaData) { + for (BlockMetadata block : parquetMetadata.getBlocks(splitStart, splitLength)) { long blockStart = block.getStartingPos(); boolean splitContainsBlock = splitStart <= blockStart && blockStart < splitStart + splitLength; if (splitContainsBlock) { 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 fe0635646f98..e543841f20eb 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 @@ -13,57 +13,27 @@ */ package io.trino.parquet.reader; -import com.google.common.collect.ImmutableList; -import io.airlift.log.Logger; import io.airlift.slice.Slice; import io.airlift.slice.Slices; import io.trino.parquet.ParquetCorruptionException; import io.trino.parquet.ParquetDataSource; import io.trino.parquet.ParquetDataSourceId; import io.trino.parquet.ParquetWriteValidation; -import io.trino.parquet.metadata.BlockMetadata; -import io.trino.parquet.metadata.ColumnChunkMetadata; import io.trino.parquet.metadata.FileMetadata; import io.trino.parquet.metadata.ParquetMetadata; import org.apache.parquet.CorruptStatistics; import org.apache.parquet.column.statistics.BinaryStatistics; -import org.apache.parquet.format.ColumnChunk; -import org.apache.parquet.format.ColumnMetaData; -import org.apache.parquet.format.Encoding; import org.apache.parquet.format.FileMetaData; -import org.apache.parquet.format.KeyValue; -import org.apache.parquet.format.RowGroup; -import org.apache.parquet.format.SchemaElement; import org.apache.parquet.format.Statistics; -import org.apache.parquet.hadoop.metadata.ColumnPath; -import org.apache.parquet.hadoop.metadata.CompressionCodecName; import org.apache.parquet.schema.LogicalTypeAnnotation; -import org.apache.parquet.schema.MessageType; import org.apache.parquet.schema.PrimitiveType; -import org.apache.parquet.schema.Type.Repetition; -import org.apache.parquet.schema.Types; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; import java.util.Optional; -import java.util.Set; -import static io.trino.parquet.ParquetMetadataConverter.convertEncodingStats; import static io.trino.parquet.ParquetMetadataConverter.fromParquetStatistics; -import static io.trino.parquet.ParquetMetadataConverter.getEncoding; -import static io.trino.parquet.ParquetMetadataConverter.getLogicalTypeAnnotation; -import static io.trino.parquet.ParquetMetadataConverter.getPrimitive; -import static io.trino.parquet.ParquetMetadataConverter.toColumnIndexReference; -import static io.trino.parquet.ParquetMetadataConverter.toOffsetIndexReference; import static io.trino.parquet.ParquetValidationUtils.validateParquet; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; @@ -73,8 +43,6 @@ public final class MetadataReader { - private static final Logger log = Logger.get(MetadataReader.class); - private static final Slice MAGIC = Slices.utf8Slice("PAR1"); private static final int POST_SCRIPT_SIZE = Integer.BYTES + MAGIC.length(); // Typical 1GB files produced by Trino were found to have footer size between 30-40KB @@ -119,144 +87,11 @@ 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()); + ParquetMetadata parquetMetadata = new ParquetMetadata(fileMetaData, dataSource.getId()); validateFileMetadata(dataSource.getId(), parquetMetadata.getFileMetaData(), parquetWriteValidation); return parquetMetadata; } - public static ParquetMetadata createParquetMetadata(FileMetaData fileMetaData, ParquetDataSourceId dataSourceId) - throws ParquetCorruptionException - { - List schema = fileMetaData.getSchema(); - validateParquet(!schema.isEmpty(), dataSourceId, "Schema is empty"); - - MessageType messageType = readParquetSchema(schema); - List blocks = new ArrayList<>(); - List rowGroups = fileMetaData.getRow_groups(); - if (rowGroups != null) { - for (RowGroup rowGroup : rowGroups) { - List columns = rowGroup.getColumns(); - validateParquet(!columns.isEmpty(), dataSourceId, "No columns in row group: %s", rowGroup); - String filePath = columns.get(0).getFile_path(); - ImmutableList.Builder columnMetadataBuilder = ImmutableList.builderWithExpectedSize(columns.size()); - 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() - .map(value -> value.toLowerCase(Locale.ENGLISH)) - .toArray(String[]::new); - ColumnPath columnPath = ColumnPath.get(path); - PrimitiveType primitiveType = messageType.getType(columnPath.toArray()).asPrimitiveType(); - ColumnChunkMetadata column = ColumnChunkMetadata.get( - columnPath, - primitiveType, - CompressionCodecName.fromParquet(metaData.codec), - convertEncodingStats(metaData.encoding_stats), - readEncodings(metaData.encodings), - readStats(Optional.ofNullable(fileMetaData.getCreated_by()), Optional.ofNullable(metaData.statistics), primitiveType), - metaData.data_page_offset, - metaData.dictionary_page_offset, - metaData.num_values, - metaData.total_compressed_size, - metaData.total_uncompressed_size); - column.setColumnIndexReference(toColumnIndexReference(columnChunk)); - column.setOffsetIndexReference(toOffsetIndexReference(columnChunk)); - column.setBloomFilterOffset(metaData.bloom_filter_offset); - columnMetadataBuilder.add(column); - } - blocks.add(new BlockMetadata(rowGroup.getNum_rows(), columnMetadataBuilder.build())); - } - } - - Map keyValueMetaData = new HashMap<>(); - List keyValueList = fileMetaData.getKey_value_metadata(); - if (keyValueList != null) { - for (KeyValue keyValue : keyValueList) { - keyValueMetaData.put(keyValue.key, keyValue.value); - } - } - FileMetadata parquetFileMetadata = new FileMetadata( - messageType, - keyValueMetaData, - fileMetaData.getCreated_by()); - return new ParquetMetadata(parquetFileMetadata, blocks); - } - - private static MessageType readParquetSchema(List schema) - { - Iterator schemaIterator = schema.iterator(); - SchemaElement rootSchema = schemaIterator.next(); - Types.MessageTypeBuilder builder = Types.buildMessage(); - readTypeSchema(builder, schemaIterator, rootSchema.getNum_children()); - return builder.named(rootSchema.name); - } - - private static void readTypeSchema(Types.GroupBuilder builder, Iterator schemaIterator, int typeCount) - { - for (int i = 0; i < typeCount; i++) { - SchemaElement element = schemaIterator.next(); - Types.Builder typeBuilder; - if (element.type == null) { - typeBuilder = builder.group(Repetition.valueOf(element.repetition_type.name())); - readTypeSchema((Types.GroupBuilder) typeBuilder, schemaIterator, element.num_children); - } - else { - Types.PrimitiveBuilder primitiveBuilder = builder.primitive(getPrimitive(element.type), Repetition.valueOf(element.repetition_type.name())); - if (element.isSetType_length()) { - primitiveBuilder.length(element.type_length); - } - if (element.isSetPrecision()) { - primitiveBuilder.precision(element.precision); - } - if (element.isSetScale()) { - primitiveBuilder.scale(element.scale); - } - typeBuilder = primitiveBuilder; - } - - // Reading of element.logicalType and element.converted_type corresponds to parquet-mr's code at - // https://github.com/apache/parquet-mr/blob/apache-parquet-1.12.0/parquet-hadoop/src/main/java/org/apache/parquet/format/converter/ParquetMetadataConverter.java#L1568-L1582 - LogicalTypeAnnotation annotationFromLogicalType = null; - if (element.isSetLogicalType()) { - annotationFromLogicalType = getLogicalTypeAnnotation(element.logicalType); - typeBuilder.as(annotationFromLogicalType); - } - if (element.isSetConverted_type()) { - LogicalTypeAnnotation annotationFromConvertedType = getLogicalTypeAnnotation(element.converted_type, element); - if (annotationFromLogicalType != null) { - // Both element.logicalType and element.converted_type set - if (annotationFromLogicalType.toOriginalType() == annotationFromConvertedType.toOriginalType()) { - // element.converted_type matches element.logicalType, even though annotationFromLogicalType may differ from annotationFromConvertedType - // Following parquet-mr behavior, we favor LogicalTypeAnnotation derived from element.logicalType, as potentially containing more information. - } - else { - // Following parquet-mr behavior, issue warning and let converted_type take precedence. - log.warn("Converted type and logical type metadata map to different OriginalType (convertedType: %s, logical type: %s). Using value in converted type.", - element.converted_type, element.logicalType); - // parquet-mr reads only OriginalType from converted_type. We retain full LogicalTypeAnnotation - // 1. for compatibility, as previous Trino reader code would read LogicalTypeAnnotation from element.converted_type and some additional fields. - // 2. so that we override LogicalTypeAnnotation annotation read from element.logicalType in case of mismatch detected. - typeBuilder.as(annotationFromConvertedType); - } - } - else { - // parquet-mr reads only OriginalType from converted_type. We retain full LogicalTypeAnnotation for compatibility, as previous - // Trino reader code would read LogicalTypeAnnotation from element.converted_type and some additional fields. - typeBuilder.as(annotationFromConvertedType); - } - } - - if (element.isSetField_id()) { - typeBuilder.id(element.field_id); - } - typeBuilder.named(element.name.toLowerCase(Locale.ENGLISH)); - } - } - public static org.apache.parquet.column.statistics.Statistics readStats(Optional fileCreatedBy, Optional statisticsFromFile, PrimitiveType type) { Statistics statistics = statisticsFromFile.orElse(null); @@ -352,15 +187,6 @@ private static int commonPrefix(byte[] a, byte[] b) return commonPrefixLength; } - private static Set readEncodings(List encodings) - { - Set columnEncodings = new HashSet<>(); - for (Encoding encoding : encodings) { - columnEncodings.add(getEncoding(encoding)); - } - return Collections.unmodifiableSet(columnEncodings); - } - private static void validateFileMetadata(ParquetDataSourceId dataSourceId, FileMetadata fileMetaData, Optional parquetWriteValidation) throws ParquetCorruptionException { 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 1eed8ba73ef1..3bf3ab7010df 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 @@ -350,7 +350,7 @@ private void flush() columnMetaDataBuilder.add(columnMetaData); currentOffset += columnMetaData.getTotal_compressed_size(); } - updateRowGroups(columnMetaDataBuilder.build()); + updateRowGroups(columnMetaDataBuilder.build(), outputStream.longSize()); // flush pages for (BufferData bufferData : bufferDataList) { @@ -409,12 +409,14 @@ private void writeBloomFilters(List rowGroups, List columnMetaData) + private void updateRowGroups(List columnMetaData, long fileOffset) { long totalCompressedBytes = columnMetaData.stream().mapToLong(ColumnMetaData::getTotal_compressed_size).sum(); long totalBytes = columnMetaData.stream().mapToLong(ColumnMetaData::getTotal_uncompressed_size).sum(); ImmutableList columnChunks = columnMetaData.stream().map(ParquetWriter::toColumnChunk).collect(toImmutableList()); - fileFooter.addRowGroup(new RowGroup(columnChunks, totalBytes, rows).setTotal_compressed_size(totalCompressedBytes)); + fileFooter.addRowGroup(new RowGroup(columnChunks, totalBytes, rows) + .setTotal_compressed_size(totalCompressedBytes) + .setFile_offset(fileOffset)); } private static Slice serializeFooter(FileMetaData fileMetaData) 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 aeda237c642f..0528adcd1166 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 @@ -155,7 +155,7 @@ public static ParquetReader createParquetReader( 0, input.getEstimatedSize(), input, - parquetMetadata.getBlocks(), + parquetMetadata, ImmutableList.of(parquetTupleDomain), ImmutableList.of(parquetPredicate), descriptorsByPath, diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReader.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReader.java index 00ecd8388857..db8b4225770a 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReader.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReader.java @@ -20,6 +20,7 @@ import io.trino.memory.context.AggregatedMemoryContext; import io.trino.parquet.ParquetDataSource; import io.trino.parquet.ParquetReaderOptions; +import io.trino.parquet.metadata.BlockMetadata; import io.trino.parquet.metadata.ParquetMetadata; import io.trino.parquet.writer.ParquetWriterOptions; import io.trino.spi.Page; @@ -186,6 +187,44 @@ public void testBackwardsCompatibleRepeatedPrimitiveFieldDefinedAsPrimitive() .isInstanceOf(TrinoException.class); } + @Test + void testReadMetadataWithSplitOffset() + throws IOException + { + // Write a file with 100 rows per row-group + List columnNames = ImmutableList.of("columna", "columnb"); + List types = ImmutableList.of(INTEGER, BIGINT); + + ParquetDataSource dataSource = new TestingParquetDataSource( + writeParquetFile( + ParquetWriterOptions.builder() + .setMaxBlockSize(DataSize.ofBytes(1000)) + .build(), + types, + columnNames, + generateInputPages(types, 100, 5)), + new ParquetReaderOptions()); + + // Read both columns, 1 row group + ParquetMetadata parquetMetadata = MetadataReader.readFooter(dataSource, Optional.empty()); + List columnBlocks = parquetMetadata.getBlocks(0, 800); + assertThat(columnBlocks.size()).isEqualTo(1); + assertThat(columnBlocks.getFirst().columns().size()).isEqualTo(2); + assertThat(columnBlocks.getFirst().rowCount()).isEqualTo(100); + + // Read both columns, half row groups + parquetMetadata = MetadataReader.readFooter(dataSource, Optional.empty()); + columnBlocks = parquetMetadata.getBlocks(0, 2500); + assertThat(columnBlocks.stream().allMatch(block -> block.columns().size() == 2)).isTrue(); + assertThat(columnBlocks.stream().mapToLong(BlockMetadata::rowCount).sum()).isEqualTo(300); + + // Read both columns, all row groups + parquetMetadata = MetadataReader.readFooter(dataSource, Optional.empty()); + columnBlocks = parquetMetadata.getBlocks(); + assertThat(columnBlocks.stream().allMatch(block -> block.columns().size() == 2)).isTrue(); + assertThat(columnBlocks.stream().mapToLong(BlockMetadata::rowCount).sum()).isEqualTo(500); + } + private void testReadingOldParquetFiles(File file, List columnNames, Type columnType, List expectedValues) throws IOException { 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 a80cbbcd00d7..2d3b7f6c1ac6 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 @@ -46,6 +46,7 @@ import org.apache.parquet.format.CompressionCodec; import org.apache.parquet.format.PageHeader; import org.apache.parquet.format.PageType; +import org.apache.parquet.format.RowGroup; import org.apache.parquet.format.Util; import org.apache.parquet.schema.PrimitiveType; import org.assertj.core.data.Percentage; @@ -379,6 +380,38 @@ public void testDictionaryPageOffset() } } + @Test + void testRowGroupOffset() + throws IOException + { + // Write a file with 100 rows per row-group + List columnNames = ImmutableList.of("columnA", "columnB"); + List types = ImmutableList.of(INTEGER, BIGINT); + + ParquetDataSource dataSource = new TestingParquetDataSource( + writeParquetFile( + ParquetWriterOptions.builder() + .setMaxBlockSize(DataSize.ofBytes(1000)) + .build(), + types, + columnNames, + generateInputPages(types, 100, 10)), + new ParquetReaderOptions()); + + ParquetMetadata parquetMetadata = MetadataReader.readFooter(dataSource, Optional.empty()); + List blocks = parquetMetadata.getBlocks(); + assertThat(blocks.size()).isGreaterThan(1); + + List rowGroups = parquetMetadata.getParquetMetadata().getRow_groups(); + assertThat(rowGroups.size()).isEqualTo(blocks.size()); + for (int rowGroupIndex = 0; rowGroupIndex < rowGroups.size(); rowGroupIndex++) { + RowGroup rowGroup = rowGroups.get(rowGroupIndex); + assertThat(rowGroup.isSetFile_offset()).isTrue(); + BlockMetadata blockMetadata = blocks.get(rowGroupIndex); + assertThat(blockMetadata.getStartingPos()).isEqualTo(rowGroup.getFile_offset()); + } + } + @ParameterizedTest @MethodSource("testWriteBloomFiltersParams") public void testWriteBloomFilters(Type type, List data) diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index 210030ac6160..44008bb79400 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 52f09777e537..ddf3dcba315f 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 @@ -201,14 +201,6 @@ public List listSchemaNames(ConnectorSession session) } } - @Override - public Optional getTableHandleForExecute(ConnectorSession session, ConnectorTableHandle tableHandle, String procedureName, Map executeProperties, RetryMode retryMode) - { - try (ThreadContextClassLoader _ = new ThreadContextClassLoader(classLoader)) { - return delegate.getTableHandleForExecute(session, tableHandle, procedureName, executeProperties, retryMode); - } - } - @Override public Optional getTableHandleForExecute(ConnectorSession session, ConnectorAccessControl accessControl, ConnectorTableHandle tableHandle, String procedureName, Map executeProperties, RetryMode retryMode) { @@ -1206,14 +1198,6 @@ public Optional getUpdateLayout(ConnectorSession se return delegate.getUpdateLayout(session, tableHandle); } - @Override - public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, RetryMode retryMode) - { - try (ThreadContextClassLoader _ = new ThreadContextClassLoader(classLoader)) { - return delegate.beginMerge(session, tableHandle, retryMode); - } - } - @Override public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, Map> updateCaseColumns, RetryMode retryMode) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/util/JsonTypeUtil.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/util/JsonTypeUtil.java index 46150e5975df..ec110a2407e7 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/util/JsonTypeUtil.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/util/JsonTypeUtil.java @@ -22,10 +22,19 @@ import io.airlift.slice.SliceOutput; import io.airlift.slice.Slices; import io.trino.spi.TrinoException; +import io.trino.spi.type.SqlDate; +import io.trino.spi.type.SqlDecimal; +import io.trino.spi.type.SqlTime; +import io.trino.spi.type.SqlTimeWithTimeZone; +import io.trino.spi.type.SqlTimestamp; +import io.trino.spi.type.SqlTimestampWithTimeZone; +import io.trino.spi.type.SqlVarbinary; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; +import java.util.List; +import java.util.StringJoiner; import static com.fasterxml.jackson.core.JsonFactory.Feature.CANONICALIZE_FIELD_NAMES; import static com.fasterxml.jackson.databind.SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS; @@ -63,10 +72,28 @@ public static Slice jsonParse(Slice slice) } } - public static Slice toJsonValue(Object value) + public static Slice toJsonValue(List values) throws IOException { - return Slices.wrappedBuffer(SORTED_MAPPER.writeValueAsBytes(value)); + if (values == null) { + return Slices.utf8Slice("[]"); + } + + StringJoiner joiner = new StringJoiner(",", "[", "]"); + for (Object value : values) { + joiner.add(switch (value) { + case null -> "null"; + case SqlDate _, + SqlTime _, + SqlVarbinary _, + SqlTimeWithTimeZone _, + SqlDecimal _, + SqlTimestamp _, + SqlTimestampWithTimeZone _ -> SORTED_MAPPER.writeValueAsString(value.toString()); + default -> SORTED_MAPPER.writeValueAsString(value); + }); + } + return Slices.utf8Slice(joiner.toString()); } private static JsonParser createJsonParser(JsonFactory factory, Slice json) diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index da3957d2241c..7a5278dc8120 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index 8251f803705a..d116c088cc21 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 59e9c9d75f5e..1f0726e30d0d 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 @@ -337,8 +337,7 @@ public void testCaseSensitiveAggregationPushdown() PlanMatchPattern aggregationOverTableScan = node(AggregationNode.class, node(TableScanNode.class)); PlanMatchPattern groupingAggregationOverTableScan = node(AggregationNode.class, node(TableScanNode.class)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_cs_agg_pushdown", "(a_string varchar(1), a_char char(1), a_bigint bigint)", ImmutableList.of( @@ -666,7 +665,7 @@ public void testCountDistinctWithStringTypes() .setSystemProperty(DISTINCT_AGGREGATIONS_STRATEGY, "pre_aggregate") .build(); - try (TestTable testTable = new TestTable(getQueryRunner()::execute, "distinct_strings", "(t_char CHAR(5), t_varchar VARCHAR(5))", rows)) { + try (TestTable testTable = newTrinoTable("distinct_strings", "(t_char CHAR(5), t_varchar VARCHAR(5))", rows)) { if (!(hasBehavior(SUPPORTS_AGGREGATION_PUSHDOWN) && hasBehavior(SUPPORTS_PREDICATE_PUSHDOWN_WITH_VARCHAR_INEQUALITY))) { // disabling hash generation to prevent extra projections in the plan which make it hard to write matchers for isNotFullyPushedDown Session optimizeHashGenerationDisabled = Session.builder(getSession()) @@ -1121,8 +1120,7 @@ public void testNullSensitiveTopNPushdown() return; } - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_null_sensitive_topn_pushdown", "(name varchar(10), a bigint)", List.of( @@ -1186,8 +1184,7 @@ public void testCaseSensitiveTopNPushdown() boolean expectTopNPushdown = hasBehavior(SUPPORTS_TOPN_PUSHDOWN_WITH_VARCHAR); PlanMatchPattern topNOverTableScan = project(node(TopNNode.class, anyTree(node(TableScanNode.class)))); - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_case_sensitive_topn_pushdown", "(a_string varchar(10), a_char char(10), a_bigint bigint)", List.of( @@ -1278,9 +1275,8 @@ public void testJoinPushdown() return; } - try (TestTable nationLowercaseTable = new TestTable( + try (TestTable nationLowercaseTable = newTrinoTable( // If a connector supports Join pushdown, but does not allow CTAS, we need to make the table creation here overridable. - getQueryRunner()::execute, "nation_lowercase", "AS SELECT nationkey, lower(name) name, regionkey FROM nation")) { for (JoinOperator joinOperator : JoinOperator.values()) { @@ -1547,12 +1543,10 @@ public void testBulkColumnListingOptions() String schemaName = "test_columns_listing_" + randomNameSuffix(); assertUpdate("CREATE SCHEMA " + schemaName); try { - try (TestTable newNation = new TestTable( - getQueryRunner()::execute, + try (TestTable newNation = newTrinoTable( schemaName + ".nation", "(name varchar(25), nationkey bigint)"); - TestTable newRegion = new TestTable( - getQueryRunner()::execute, + TestTable newRegion = newTrinoTable( schemaName + ".region", "(name varchar(25), regionkey bigint)")) { if (hasBehavior(SUPPORTS_COMMENT_ON_TABLE)) { @@ -1755,7 +1749,7 @@ public void testUpdateNotNullColumn() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "update_not_null", "(nullable_col INTEGER, not_null_col INTEGER NOT NULL)")) { + try (TestTable table = newTrinoTable("update_not_null", "(nullable_col INTEGER, not_null_col INTEGER NOT NULL)")) { assertUpdate(format("INSERT INTO %s (nullable_col, not_null_col) VALUES (1, 10)", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES (1, 10)"); assertQueryFails("UPDATE " + table.getName() + " SET not_null_col = NULL WHERE nullable_col = 1", MODIFYING_ROWS_MESSAGE); @@ -1774,7 +1768,7 @@ public void testUpdateRowType() } skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_UPDATE) && hasBehavior(SUPPORTS_ROW_TYPE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_update_with_predicates_on_row_types", "(int_t INT, row_t ROW(f1 INT, f2 INT))")) { + try (TestTable table = newTrinoTable("test_update_with_predicates_on_row_types", "(int_t INT, row_t ROW(f1 INT, f2 INT))")) { String tableName = table.getName(); assertUpdate("INSERT INTO " + tableName + " VALUES (1, ROW(2, 3)), (11, ROW(12, 13)), (21, ROW(22, 23))", 3); assertQueryFails("UPDATE " + tableName + " SET int_t = int_t - 1 WHERE row_t.f2 = 3", MODIFYING_ROWS_MESSAGE); @@ -1793,7 +1787,7 @@ public void testUpdateRowConcurrently() } skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_UPDATE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_update_row", "(a INT, b INT, c INT)", ImmutableList.of("1, 2, 3"))) { + try (TestTable table = newTrinoTable("test_update_row", "(a INT, b INT, c INT)", ImmutableList.of("1, 2, 3"))) { assertQueryFails("UPDATE " + table.getName() + " SET a = a + 1", MODIFYING_ROWS_MESSAGE); } } @@ -1809,7 +1803,7 @@ public void testUpdateAllValues() } skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_UPDATE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_update_all", "(a INT, b INT, c INT)", ImmutableList.of("1, 2, 3"))) { + try (TestTable table = newTrinoTable("test_update_all", "(a INT, b INT, c INT)", ImmutableList.of("1, 2, 3"))) { assertUpdate("UPDATE " + table.getName() + " SET a = 1, b = 1, c = 2", 1); } } @@ -1826,7 +1820,7 @@ public void testUpdateWithPredicates() } skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_UPDATE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_row_predicates", "(a INT, b INT, c INT)")) { + try (TestTable table = newTrinoTable("test_row_predicates", "(a INT, b INT, c INT)")) { String tableName = table.getName(); assertUpdate("INSERT INTO " + tableName + " VALUES (1, 2, 3), (11, 12, 13), (21, 22, 23)", 3); assertUpdate("UPDATE " + tableName + " SET a = 5 WHERE c = 3", 1); @@ -1861,7 +1855,7 @@ public void testUpdateWithPredicates() public void testConstantUpdateWithVarcharEqualityPredicates() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_UPDATE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_update_varchar", "(col1 INT, col2 varchar(1))", ImmutableList.of("1, 'a'", "2, 'A'"))) { + try (TestTable table = newTrinoTable("test_update_varchar", "(col1 INT, col2 varchar(1))", ImmutableList.of("1, 'a'", "2, 'A'"))) { if (!hasBehavior(SUPPORTS_PREDICATE_PUSHDOWN_WITH_VARCHAR_EQUALITY)) { assertQueryFails("UPDATE " + table.getName() + " SET col1 = 20 WHERE col2 = 'A'", MODIFYING_ROWS_MESSAGE); return; @@ -1910,7 +1904,7 @@ public void testDeleteWithBigintEqualityPredicate() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_ROW_LEVEL_DELETE)); // TODO (https://github.com/trinodb/trino/issues/5901) Use longer table name once Oracle version is updated - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_bigint", "AS SELECT * FROM region")) { + try (TestTable table = newTrinoTable("test_delete_bigint", "AS SELECT * FROM region")) { assertUpdate("DELETE FROM " + table.getName() + " WHERE regionkey = 1", 1); assertQuery( "SELECT regionkey, name FROM " + table.getName(), @@ -1927,7 +1921,7 @@ public void testDeleteWithVarcharEqualityPredicate() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_ROW_LEVEL_DELETE)); // TODO (https://github.com/trinodb/trino/issues/5901) Use longer table name once Oracle version is updated - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_varchar", "(col varchar(1))", ImmutableList.of("'a'", "'A'", "null"))) { + try (TestTable table = newTrinoTable("test_delete_varchar", "(col varchar(1))", ImmutableList.of("'a'", "'A'", "null"))) { if (!hasBehavior(SUPPORTS_PREDICATE_PUSHDOWN_WITH_VARCHAR_EQUALITY)) { assertQueryFails("DELETE FROM " + table.getName() + " WHERE col = 'A'", MODIFYING_ROWS_MESSAGE); return; @@ -2042,8 +2036,7 @@ public void testInsertWithoutTemporaryTable() .setCatalogSessionProperty(getSession().getCatalog().orElseThrow(), "non_transactional_insert", "false") .build(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_bypass_temp", "(a varchar(36), b bigint)")) { int numberOfRows = 50; @@ -2072,8 +2065,7 @@ private void testWriteBatchSizeSessionProperty(int batchSize, int numberOfRows) .setCatalogSessionProperty(getSession().getCatalog().orElseThrow(), "write_batch_size", Integer.toString(batchSize)) .build(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "write_batch_size", "(a varchar(36), b bigint)")) { String values = String.join(",", buildRowsForInsert(numberOfRows)); @@ -2103,8 +2095,7 @@ private void testWriteTaskParallelismSessionProperty(int parallelism, int number .build(); QueryRunner queryRunner = getQueryRunner(); - try (TestTable table = new TestTable( - queryRunner::execute, + try (TestTable table = newTrinoTable( "write_parallelism", "(a varchar(128), b bigint)")) { Plan plan = newTransaction() @@ -2274,8 +2265,8 @@ public void testJoinPushdownWithLongIdentifiers() .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))) { + try (TestTable left = newTrinoTable("test_long_id_l", format("(%s BIGINT)", validColumnName)); + TestTable right = newTrinoTable("test_long_id_r", format("(%s BIGINT)", validColumnName))) { assertThat(query(joinPushdownEnabled(getSession()), """ SELECT l.%1$s, r.%1$s @@ -2403,8 +2394,7 @@ public void testDynamicFilteringCaseInsensitiveDomainCompaction() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); skipTestUnless(hasBehavior(SUPPORTS_DYNAMIC_FILTER_PUSHDOWN)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_caseinsensitive", "(id varchar(1))", ImmutableList.of("'0'", "'a'", "'B'"))) { diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcTableStatisticsTest.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcTableStatisticsTest.java index 243038d66d3e..8cfd98769680 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcTableStatisticsTest.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcTableStatisticsTest.java @@ -171,8 +171,7 @@ public void testStatsWithVarcharPredicatePushdown() "('comment', 1e0, 0e0, null)," + "(null, null, null, 1e0)"); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "varchar_duplicates", // each letter A-E repeated 5 times " AS SELECT nationkey, chr(codepoint('A') + nationkey / 5) fl FROM tpch.tiny.nation")) { diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index 3273806736df..9f5f027897db 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 48646ef1c81f..f81bbac4a50a 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 @@ -205,7 +205,7 @@ public void testCreateTableWithRowTypeWithoutField() @Test public void testCreateTableAlreadyExists() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_table_already_exists", "(col1 int)")) { + try (TestTable table = newTrinoTable("test_create_table_already_exists", "(col1 int)")) { assertQueryFails( "CREATE TABLE " + table.getName() + "(col1 int)", "\\Qline 1:1: Table 'bigquery.tpch." + table.getName() + "' already exists\\E"); @@ -387,7 +387,7 @@ public void testStreamCommentTableSpecialCharacter() @Override // Override because the base test exceeds rate limits per a table public void testCommentColumn() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_comment_column_", "(a integer)")) { + try (TestTable table = newTrinoTable("test_comment_column_", "(a integer)")) { // comment set assertUpdate("COMMENT ON COLUMN " + table.getName() + ".a IS 'new comment'"); assertThat((String) computeScalar("SHOW CREATE TABLE " + table.getName())).contains("COMMENT 'new comment'"); @@ -398,7 +398,7 @@ public void testCommentColumn() assertThat(getColumnComment(table.getName(), "a")).isEqualTo(null); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_comment_column_", "(a integer COMMENT 'test comment')")) { + try (TestTable table = newTrinoTable("test_comment_column_", "(a integer COMMENT 'test comment')")) { assertThat(getColumnComment(table.getName(), "a")).isEqualTo("test comment"); // comment set new value assertUpdate("COMMENT ON COLUMN " + table.getName() + ".a IS 'updated comment'"); @@ -550,7 +550,7 @@ public void testPredicatePushdownPrunnedColumns() @Test public void testColumnPositionMismatch() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test.test_column_position_mismatch", "(c_varchar VARCHAR, c_int INT, c_date DATE)")) { + try (TestTable table = newTrinoTable("test.test_column_position_mismatch", "(c_varchar VARCHAR, c_int INT, c_date DATE)")) { onBigQuery("INSERT INTO " + table.getName() + " VALUES ('a', 1, '2021-01-01')"); // Adding a CAST makes BigQuery return columns in a different order assertQuery("SELECT c_varchar, CAST(c_int AS SMALLINT), c_date FROM " + table.getName(), "VALUES ('a', 1, '2021-01-01')"); @@ -1458,7 +1458,7 @@ private void assertLimitPushdownReadsLessData(Session session, String tableName) public void testInsertArray() { // Override because the connector disallows writing a NULL in ARRAY - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_array_", "(a ARRAY, b ARRAY)")) { + try (TestTable table = newTrinoTable("test_insert_array_", "(a ARRAY, b ARRAY)")) { assertUpdate("INSERT INTO " + table.getName() + " (a, b) VALUES (ARRAY[1.23E1], ARRAY[1.23E1])", 1); assertQuery("SELECT a[1], b[1] FROM " + table.getName(), "VALUES (12.3, 12)"); } 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 9724da1b9b55..d23d22a4d099 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 @@ -738,7 +738,7 @@ public void testArray() @Test public void testArrayType() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_array_", "(a BIGINT, b ARRAY, c ARRAY)")) { + try (TestTable table = newTrinoTable("test_array_", "(a BIGINT, b ARRAY, c ARRAY)")) { assertUpdate("INSERT INTO " + table.getName() + " (a, b, c) VALUES (5, ARRAY[1.23E1], ARRAY[15]), (6, ARRAY[1.24E1, 1.27E1, 2.23E1], ARRAY[25, 26, 36])", 2); assertThat(query("SELECT * FROM " + table.getName())) .matches("VALUES " + @@ -752,7 +752,7 @@ public void testUnsupportedNullArray() { // BigQuery translates a NULL ARRAY into an empty ARRAY in the query result // This test ensures that the connector disallows writing a NULL ARRAY - try (TestTable table = new TestTable(getQueryRunner()::execute, "test.test_null_array", "(col ARRAY(INT))")) { + try (TestTable table = newTrinoTable("test.test_null_array", "(col ARRAY(INT))")) { assertQueryFails("INSERT INTO " + table.getName() + " VALUES (NULL)", "NULL value not allowed for NOT NULL column: col"); } } diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryWithProxyTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryWithProxyTest.java index a875ff62f53d..cd98f539c8c2 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryWithProxyTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryWithProxyTest.java @@ -48,7 +48,7 @@ protected QueryRunner createQueryRunner() void testCreateTableAsSelect() { // This test covers all client (BigQuery, BigQueryReadClient and BigQueryWriteClient) - try (TestTable table = new TestTable(getQueryRunner()::execute, "test.test_ctas", "AS SELECT 42 x")) { + try (TestTable table = newTrinoTable("test.test_ctas", "AS SELECT 42 x")) { assertQuery("SELECT * FROM " + table.getName(), "VALUES 42"); } } diff --git a/plugin/trino-blackhole/pom.xml b/plugin/trino-blackhole/pom.xml index 09ac4505dba2..95203943a402 100644 --- a/plugin/trino-blackhole/pom.xml +++ b/plugin/trino-blackhole/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-blackhole/src/main/java/io/trino/plugin/blackhole/BlackHoleMetadata.java b/plugin/trino-blackhole/src/main/java/io/trino/plugin/blackhole/BlackHoleMetadata.java index ece7a0f39afc..9d4220ee7b43 100644 --- a/plugin/trino-blackhole/src/main/java/io/trino/plugin/blackhole/BlackHoleMetadata.java +++ b/plugin/trino-blackhole/src/main/java/io/trino/plugin/blackhole/BlackHoleMetadata.java @@ -401,7 +401,7 @@ public ColumnHandle getMergeRowIdColumnHandle(ConnectorSession session, Connecto } @Override - public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, RetryMode retryMode) + public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, Map> updateCaseColumns, RetryMode retryMode) { return new BlackHoleMergeTableHandle((BlackHoleTableHandle) tableHandle); } diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index 3e8fe2860f73..a5d02719359b 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 e4dbdc5fc0b4..a9d4ce254ee7 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 @@ -76,7 +76,7 @@ public void testRowLevelDelete() @Test public void testInsertDate() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_", "(a_date date)")) { + try (TestTable table = newTrinoTable("test_insert_", "(a_date date)")) { assertUpdate("INSERT INTO " + table.getName() + " (a_date) VALUES ( DATE '2020-05-11')", 1); assertThat(query("SELECT a_date FROM " + table.getName())).matches("VALUES (DATE '2020-05-11')"); } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraProtocolVersionV3ConnectorSmokeTest.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraProtocolVersionV3ConnectorSmokeTest.java index 428291628ea1..7ba3a1e68795 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraProtocolVersionV3ConnectorSmokeTest.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraProtocolVersionV3ConnectorSmokeTest.java @@ -43,7 +43,7 @@ protected QueryRunner createQueryRunner() @Override public void testInsertDate() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_", "(a_date date)")) { + try (TestTable table = newTrinoTable("test_insert_", "(a_date date)")) { assertUpdate("INSERT INTO " + table.getName() + " (a_date) VALUES ('2020-05-11')", 1); assertThat(query("SELECT a_date FROM " + table.getName())).matches("VALUES (CAST('2020-05-11' AS varchar))"); } diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index 675473440ecb..860d259d6fb0 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 2444ea4bf6c6..90f9744c288d 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 @@ -889,7 +889,7 @@ private void testUnsupportedDate(String unsupportedDate) String minSupportedDate = "1970-01-01"; String maxSupportedDate = "2149-06-06"; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_unsupported_date", "(dt date)")) { + try (TestTable table = newTrinoTable("test_unsupported_date", "(dt date)")) { assertQueryFails( format("INSERT INTO %s VALUES (DATE '%s')", table.getName(), unsupportedDate), format("Date must be between %s and %s in ClickHouse: %s", minSupportedDate, maxSupportedDate, unsupportedDate)); @@ -984,7 +984,7 @@ public void testUnsupportedTimestamp(String unsupportedTimestamp) String minSupportedTimestamp = "1970-01-01 00:00:00"; String maxSupportedTimestamp = "2106-02-07 06:28:15"; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_unsupported_timestamp", "(dt timestamp(0))")) { + try (TestTable table = newTrinoTable("test_unsupported_timestamp", "(dt timestamp(0))")) { assertQueryFails( format("INSERT INTO %s VALUES (TIMESTAMP '%s')", table.getName(), unsupportedTimestamp), format("Timestamp must be between %s and %s in ClickHouse: %s", minSupportedTimestamp, maxSupportedTimestamp, unsupportedTimestamp)); 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 6f5e0093c65b..4069feced7f8 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 @@ -126,8 +126,7 @@ public void testRenameColumn() @Override public void testRenameColumnWithComment() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_rename_column_", "(id INT NOT NULL, col INT COMMENT 'test column comment') WITH (engine = 'MergeTree', order_by = ARRAY['id'])")) { assertThat(getColumnComment(table.getName(), "col")).isEqualTo("test column comment"); @@ -141,7 +140,7 @@ public void testRenameColumnWithComment() 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'])")) { + try (TestTable table = newTrinoTable("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)); assertThat(getColumnComment(table.getName(), "b_varchar")).isEqualTo(comment); } @@ -151,7 +150,7 @@ public void testAddColumnWithCommentSpecialCharacter(String comment) @Override public void testDropAndAddColumnWithSameName() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_add_column", "(x int NOT NULL, y int, z int) WITH (engine = 'MergeTree', order_by = ARRAY['x'])", ImmutableList.of("1,2,3"))) { + try (TestTable table = newTrinoTable("test_drop_add_column", "(x int NOT NULL, y int, z int) WITH (engine = 'MergeTree', order_by = ARRAY['x'])", ImmutableList.of("1,2,3"))) { assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN y"); assertQuery("SELECT * FROM " + table.getName(), "VALUES (1, 3)"); @@ -210,7 +209,7 @@ public void testDropColumn() @Override protected TestTable createTableWithOneIntegerColumn(String namePrefix) { - return new TestTable(getQueryRunner()::execute, namePrefix, "(col integer NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['col'])"); + return newTrinoTable(namePrefix, "(col integer NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['col'])"); } @Override @@ -223,7 +222,7 @@ protected String tableDefinitionForAddColumn() @Override // Overridden because the default storage type doesn't support adding columns public void testAddNotNullColumnToEmptyTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_notnull_col_to_empty", "(a_varchar varchar NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['a_varchar'])")) { + try (TestTable table = newTrinoTable("test_add_notnull_col_to_empty", "(a_varchar varchar NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['a_varchar'])")) { String tableName = table.getName(); assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN b_varchar varchar NOT NULL"); @@ -239,7 +238,7 @@ public void testAddNotNullColumnToEmptyTable() @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() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_notnull_col", "(a_varchar varchar NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['a_varchar'])")) { + try (TestTable table = newTrinoTable("test_add_notnull_col", "(a_varchar varchar NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['a_varchar'])")) { String tableName = table.getName(); assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN b_varchar varchar NOT NULL"); @@ -259,7 +258,7 @@ public void testAddNotNullColumn() public void testAddColumnWithComment() { // Override because the default storage type doesn't support adding columns - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_col_desc_", "(a_varchar varchar NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['a_varchar'])")) { + try (TestTable table = newTrinoTable("test_add_col_desc_", "(a_varchar varchar NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['a_varchar'])")) { String tableName = table.getName(); assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN b_varchar varchar COMMENT 'test new column comment'"); @@ -532,8 +531,7 @@ public void testTableProperty() public void testSetTableProperties() throws Exception { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_alter_table_properties", "(p1 int NOT NULL, p2 boolean NOT NULL, x VARCHAR) WITH (engine = 'MergeTree', order_by = ARRAY['p1', 'p2'], primary_key = ARRAY['p1', 'p2'])")) { assertThat(getTableProperties("tpch", table.getName())) @@ -558,8 +556,7 @@ public void testSetTableProperties() @Test public void testAlterInvalidTableProperties() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_alter_table_properties", "(p1 int NOT NULL, p2 int NOT NULL, x VARCHAR) WITH (engine = 'MergeTree', order_by = ARRAY['p1', 'p2'], primary_key = ARRAY['p1', 'p2'])")) { assertQueryFails( @@ -644,7 +641,7 @@ protected TestTable createTableWithDoubleAndRealColumns(String name, List io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -88,7 +88,7 @@ io.delta delta-kernel-api - 3.2.1 + 3.3.0 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 14a0f162b55a..0029fda7970d 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 @@ -24,12 +24,11 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.metastore.Partitions; import io.trino.parquet.writer.ParquetWriterOptions; import io.trino.plugin.deltalake.DataFileInfo.DataFileType; import io.trino.plugin.deltalake.util.DeltaLakeWriteUtils; -import io.trino.plugin.hive.HivePartitionKey; import io.trino.plugin.hive.parquet.ParquetFileWriter; -import io.trino.plugin.hive.util.HiveUtil; import io.trino.spi.Page; import io.trino.spi.PageIndexer; import io.trino.spi.PageIndexerFactory; @@ -56,13 +55,14 @@ import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.wrappedBuffer; +import static io.trino.metastore.Partitions.HIVE_DEFAULT_DYNAMIC_PARTITION; +import static io.trino.metastore.Partitions.escapePathName; import static io.trino.plugin.deltalake.DeltaLakeErrorCode.DELTA_LAKE_BAD_WRITE; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.getCompressionCodec; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.getParquetWriterBlockSize; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.getParquetWriterPageSize; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.getParquetWriterPageValueCount; import static io.trino.plugin.deltalake.DeltaLakeTypes.toParquetType; -import static io.trino.plugin.hive.util.HiveUtil.escapePathName; import static java.lang.Math.min; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -442,7 +442,7 @@ protected void closeWriter(int writerIndex) } /** - * Copy of {@link HiveUtil#makePartName} modified to preserve case of partition columns. + * Copy of {@link Partitions#makePartName} modified to preserve case of partition columns. */ private static String makePartName(List partitionColumns, List partitionValues) { @@ -464,7 +464,7 @@ private static String makePartName(List partitionColumns, List p public static List createPartitionValues(List partitionColumnTypes, Page partitionColumns, int position) { return DeltaLakeWriteUtils.createPartitionValues(partitionColumnTypes, partitionColumns, position).stream() - .map(value -> value.equals(HivePartitionKey.HIVE_DEFAULT_DYNAMIC_PARTITION) ? null : value) + .map(value -> value.equals(HIVE_DEFAULT_DYNAMIC_PARTITION) ? null : value) .collect(toList()); } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/BaseTransactionsTable.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/BaseTransactionsTable.java index f33fb4e84a05..387d1f0d0885 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/BaseTransactionsTable.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/BaseTransactionsTable.java @@ -14,12 +14,13 @@ package io.trino.plugin.deltalake; import com.google.common.collect.ImmutableList; +import io.airlift.units.DataSize; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry; import io.trino.plugin.deltalake.transactionlog.TableSnapshot; import io.trino.plugin.deltalake.transactionlog.Transaction; import io.trino.plugin.deltalake.transactionlog.TransactionLogAccess; +import io.trino.plugin.deltalake.transactionlog.TransactionLogEntries; import io.trino.plugin.deltalake.util.PageListBuilder; import io.trino.spi.Page; import io.trino.spi.TrinoException; @@ -144,7 +145,7 @@ public ConnectorPageSource pageSource(ConnectorTransactionHandle transactionHand PageListBuilder pagesBuilder = PageListBuilder.forTable(tableMetadata); try { List transactions = loadNewTailBackward(fileSystem, tableLocation, startVersionExclusive, endVersionInclusive.get()).reversed(); - return new FixedPageSource(buildPages(session, pagesBuilder, transactions)); + return new FixedPageSource(buildPages(session, pagesBuilder, transactions, fileSystem)); } catch (TrinoException e) { throw e; @@ -170,7 +171,7 @@ private static List loadNewTailBackward( boolean endOfHead = false; while (!endOfHead) { - Optional> results = getEntriesFromJson(entryNumber, transactionLogDir, fileSystem); + Optional results = getEntriesFromJson(entryNumber, transactionLogDir, fileSystem, DataSize.of(0, DataSize.Unit.BYTE)); if (results.isPresent()) { transactionsBuilder.add(new Transaction(version, results.get())); version = entryNumber; @@ -187,5 +188,6 @@ private static List loadNewTailBackward( return transactionsBuilder.build(); } - protected abstract List buildPages(ConnectorSession session, PageListBuilder pagesBuilder, List transactions); + protected abstract List buildPages(ConnectorSession session, PageListBuilder pagesBuilder, List transactions, TrinoFileSystem fileSystem) + throws IOException; } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeCommitSummary.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeCommitSummary.java index 30d8423c9bc7..fddc6afac65c 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeCommitSummary.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeCommitSummary.java @@ -15,15 +15,19 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import io.trino.filesystem.TrinoFileSystem; import io.trino.plugin.deltalake.transactionlog.CommitInfoEntry; import io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry; import io.trino.plugin.deltalake.transactionlog.MetadataEntry; import io.trino.plugin.deltalake.transactionlog.ProtocolEntry; +import io.trino.plugin.deltalake.transactionlog.TransactionLogEntries; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.canonicalizePartitionValues; import static java.util.Objects.requireNonNull; @@ -38,7 +42,7 @@ public class DeltaLakeCommitSummary private final Set>> addedFilesCanonicalPartitionValues; private final Optional isBlindAppend; - public DeltaLakeCommitSummary(long version, List transactionLogEntries) + public DeltaLakeCommitSummary(long version, TransactionLogEntries transactionLogEntries, TrinoFileSystem fileSystem) { requireNonNull(transactionLogEntries, "transactionLogEntries is null"); ImmutableList.Builder metadataUpdatesBuilder = ImmutableList.builder(); @@ -48,26 +52,29 @@ public DeltaLakeCommitSummary(long version, List t ImmutableSet.Builder>> removedFilesCanonicalPartitionValuesBuilder = ImmutableSet.builder(); boolean containsRemoveFileWithoutPartitionValues = false; - for (DeltaLakeTransactionLogEntry transactionLogEntry : transactionLogEntries) { - if (transactionLogEntry.getMetaData() != null) { - metadataUpdatesBuilder.add(transactionLogEntry.getMetaData()); - } - else if (transactionLogEntry.getProtocol() != null) { - optionalProtocol = Optional.of(transactionLogEntry.getProtocol()); - } - else if (transactionLogEntry.getCommitInfo() != null) { - optionalCommitInfo = Optional.of(transactionLogEntry.getCommitInfo()); - } - else if (transactionLogEntry.getAdd() != null) { - addedFilesCanonicalPartitionValuesBuilder.add(transactionLogEntry.getAdd().getCanonicalPartitionValues()); - } - else if (transactionLogEntry.getRemove() != null) { - Map partitionValues = transactionLogEntry.getRemove().partitionValues(); - if (partitionValues == null) { - containsRemoveFileWithoutPartitionValues = true; + try (Stream logEntryStream = transactionLogEntries.getEntries(fileSystem)) { + for (Iterator it = logEntryStream.iterator(); it.hasNext(); ) { + DeltaLakeTransactionLogEntry transactionLogEntry = it.next(); + if (transactionLogEntry.getMetaData() != null) { + metadataUpdatesBuilder.add(transactionLogEntry.getMetaData()); + } + else if (transactionLogEntry.getProtocol() != null) { + optionalProtocol = Optional.of(transactionLogEntry.getProtocol()); + } + else if (transactionLogEntry.getCommitInfo() != null) { + optionalCommitInfo = Optional.of(transactionLogEntry.getCommitInfo()); + } + else if (transactionLogEntry.getAdd() != null) { + addedFilesCanonicalPartitionValuesBuilder.add(transactionLogEntry.getAdd().getCanonicalPartitionValues()); } - else { - removedFilesCanonicalPartitionValuesBuilder.add(canonicalizePartitionValues(partitionValues)); + else if (transactionLogEntry.getRemove() != null) { + Map partitionValues = transactionLogEntry.getRemove().partitionValues(); + if (partitionValues == null) { + containsRemoveFileWithoutPartitionValues = true; + } + else { + removedFilesCanonicalPartitionValuesBuilder.add(canonicalizePartitionValues(partitionValues)); + } } } } 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 870703998c4c..1d553252dfe8 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 @@ -50,6 +50,7 @@ public class DeltaLakeConfig { public static final String EXTENDED_STATISTICS_ENABLED = "delta.extended-statistics.enabled"; public static final String VACUUM_MIN_RETENTION = "delta.vacuum.min-retention"; + public static final DataSize DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE = DataSize.of(16, MEGABYTE); // Runtime.getRuntime().maxMemory() is not 100% stable and may return slightly different value over JVM lifetime. We use // constant so default configuration for cache size is stable. @@ -60,6 +61,7 @@ public class DeltaLakeConfig private Duration metadataCacheTtl = new Duration(30, TimeUnit.MINUTES); private DataSize metadataCacheMaxRetainedSize = DEFAULT_METADATA_CACHE_MAX_RETAINED_SIZE; + private DataSize transactionLogMaxCachedFileSize = DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE; private DataSize dataFileCacheSize = DEFAULT_DATA_FILE_CACHE_SIZE; private Duration dataFileCacheTtl = new Duration(30, TimeUnit.MINUTES); private int domainCompactionThreshold = 1000; @@ -78,7 +80,7 @@ public class DeltaLakeConfig private boolean tableStatisticsEnabled = true; private boolean extendedStatisticsEnabled = true; private boolean collectExtendedStatisticsOnWrite = true; - private HiveCompressionCodec compressionCodec = HiveCompressionCodec.SNAPPY; + private HiveCompressionCodec compressionCodec = HiveCompressionCodec.ZSTD; private long perTransactionMetastoreCacheMaximumSize = 1000; private boolean storeTableMetadataEnabled; private int storeTableMetadataThreads = 5; @@ -121,6 +123,19 @@ public DeltaLakeConfig setMetadataCacheMaxRetainedSize(DataSize metadataCacheMax return this; } + public DataSize getTransactionLogMaxCachedFileSize() + { + return transactionLogMaxCachedFileSize; + } + + @Config("delta.transaction-log.max-cached-file-size") + @ConfigDescription("Maximum size of delta transaction log file that will be cached in memory") + public DeltaLakeConfig setTransactionLogMaxCachedFileSize(DataSize transactionLogMaxCachedFileSize) + { + this.transactionLogMaxCachedFileSize = transactionLogMaxCachedFileSize; + return this; + } + public DataSize getDataFileCacheSize() { return dataFileCacheSize; diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeHistoryTable.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeHistoryTable.java index 3c5050dc1bf4..7f048eb4d1cd 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeHistoryTable.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeHistoryTable.java @@ -14,6 +14,7 @@ package io.trino.plugin.deltalake; import com.google.common.collect.ImmutableList; +import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.deltalake.transactionlog.CommitInfoEntry; import io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry; @@ -30,6 +31,7 @@ import java.util.List; import java.util.Objects; +import java.util.stream.Stream; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.spi.type.BigintType.BIGINT; @@ -74,13 +76,15 @@ public DeltaLakeHistoryTable( } @Override - protected List buildPages(ConnectorSession session, PageListBuilder pagesBuilder, List transactions) + protected List buildPages(ConnectorSession session, PageListBuilder pagesBuilder, List transactions, TrinoFileSystem fileSystem) { - List commitInfoEntries = transactions.stream() - .flatMap(transaction -> transaction.transactionEntries().stream()) + List commitInfoEntries; + try (Stream commitStream = transactions.stream() + .flatMap(transaction -> transaction.transactionEntries().getEntries(fileSystem)) .map(DeltaLakeTransactionLogEntry::getCommitInfo) - .filter(Objects::nonNull) - .collect(toImmutableList()); + .filter(Objects::nonNull)) { + commitInfoEntries = commitStream.collect(toImmutableList()); + } TimeZoneKey timeZoneKey = session.getTimeZoneKey(); 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 0fd44423d423..4f3da6a15d7b 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 @@ -69,13 +69,14 @@ import io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport; import io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.ColumnMappingMode; import io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.UnsupportedTypeException; -import io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry; 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.TableSnapshot; import io.trino.plugin.deltalake.transactionlog.TransactionLogAccess; +import io.trino.plugin.deltalake.transactionlog.TransactionLogEntries; import io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointWriterManager; +import io.trino.plugin.deltalake.transactionlog.checkpoint.MetadataAndProtocolEntries; import io.trino.plugin.deltalake.transactionlog.statistics.DeltaLakeFileStatistics; import io.trino.plugin.deltalake.transactionlog.statistics.DeltaLakeJsonFileStatistics; import io.trino.plugin.deltalake.transactionlog.writer.TransactionConflictException; @@ -124,7 +125,6 @@ import io.trino.spi.connector.SystemTable; import io.trino.spi.connector.TableColumnsMetadata; import io.trino.spi.connector.TableNotFoundException; -import io.trino.spi.connector.TableScanRedirectApplicationResult; import io.trino.spi.connector.ViewNotFoundException; import io.trino.spi.connector.WriterScalingOptions; import io.trino.spi.expression.ConnectorExpression; @@ -197,6 +197,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Sets.difference; import static com.google.common.primitives.Ints.max; +import static io.airlift.units.DataSize.Unit.BYTE; import static io.trino.filesystem.Locations.appendPath; import static io.trino.filesystem.Locations.areDirectoryLocationsEquivalent; import static io.trino.hive.formats.HiveClassNames.HIVE_SEQUENCEFILE_OUTPUT_FORMAT_CLASS; @@ -290,8 +291,6 @@ import static io.trino.plugin.deltalake.transactionlog.TransactionLogParser.getMandatoryCurrentVersion; import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.getTransactionLogDir; import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.getTransactionLogJsonEntryPath; -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.deltalake.transactionlog.checkpoint.TransactionLogTail.getEntriesFromJson; import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; @@ -438,7 +437,6 @@ public class DeltaLakeMetadata private final String nodeVersion; private final String nodeId; private final AtomicReference rollbackAction = new AtomicReference<>(); - private final DeltaLakeRedirectionsProvider deltaLakeRedirectionsProvider; private final CachingExtendedStatisticsAccess statisticsAccess; private final boolean deleteSchemaLocationsFallback; private final boolean useUniqueTableLocation; @@ -474,7 +472,6 @@ public DeltaLakeMetadata( CheckpointWriterManager checkpointWriterManager, long defaultCheckpointInterval, boolean deleteSchemaLocationsFallback, - DeltaLakeRedirectionsProvider deltaLakeRedirectionsProvider, CachingExtendedStatisticsAccess statisticsAccess, DeltaLakeTableMetadataScheduler metadataScheduler, boolean useUniqueTableLocation, @@ -497,7 +494,6 @@ public DeltaLakeMetadata( this.nodeId = nodeManager.getCurrentNode().getNodeIdentifier(); this.checkpointWriterManager = requireNonNull(checkpointWriterManager, "checkpointWriterManager is null"); this.defaultCheckpointInterval = defaultCheckpointInterval; - this.deltaLakeRedirectionsProvider = requireNonNull(deltaLakeRedirectionsProvider, "deltaLakeRedirectionsProvider is null"); this.statisticsAccess = requireNonNull(statisticsAccess, "statisticsAccess is null"); this.deleteSchemaLocationsFallback = deleteSchemaLocationsFallback; this.metadataScheduler = requireNonNull(metadataScheduler, "metadataScheduler is null"); @@ -630,15 +626,9 @@ public LocatedTableHandle getTableHandle( TrinoFileSystem fileSystem = fileSystemFactory.create(session); TableSnapshot tableSnapshot = getSnapshot(session, tableName, tableLocation, endVersion.map(version -> getVersion(fileSystem, tableLocation, version))); - Map, Object> logEntries; + MetadataAndProtocolEntries logEntries; try { - logEntries = transactionLogAccess.getTransactionLogEntries( - session, - tableSnapshot, - ImmutableSet.of(METADATA, PROTOCOL), - entryStream -> entryStream - .filter(entry -> entry.getMetaData() != null || entry.getProtocol() != null) - .map(entry -> firstNonNull(entry.getMetaData(), entry.getProtocol()))); + logEntries = transactionLogAccess.getMetadataAndProtocolEntry(session, tableSnapshot); } catch (TrinoException e) { if (e.getErrorCode().equals(DELTA_LAKE_INVALID_SCHEMA.toErrorCode())) { @@ -646,11 +636,11 @@ public LocatedTableHandle getTableHandle( } throw e; } - MetadataEntry metadataEntry = (MetadataEntry) logEntries.get(MetadataEntry.class); + MetadataEntry metadataEntry = logEntries.metadata().orElse(null); if (metadataEntry == null) { return new CorruptedDeltaLakeTableHandle(tableName, managed, tableLocation, new TrinoException(DELTA_LAKE_INVALID_SCHEMA, "Metadata not found in transaction log for " + tableSnapshot.getTable())); } - ProtocolEntry protocolEntry = (ProtocolEntry) logEntries.get(ProtocolEntry.class); + ProtocolEntry protocolEntry = logEntries.protocol().orElse(null); if (protocolEntry == null) { return new CorruptedDeltaLakeTableHandle(tableName, managed, tableLocation, new TrinoException(DELTA_LAKE_INVALID_SCHEMA, "Protocol not found in transaction log for " + tableSnapshot.getTable())); } @@ -2267,16 +2257,16 @@ private void checkForConcurrentTransactionConflicts( if (currentVersion > readVersionValue) { String transactionLogDirectory = getTransactionLogDir(tableLocation); for (long version = readVersionValue + 1; version <= currentVersion; version++) { - List transactionLogEntries; + TransactionLogEntries transactionLogEntries; try { long finalVersion = version; - transactionLogEntries = getEntriesFromJson(version, transactionLogDirectory, fileSystem) + transactionLogEntries = getEntriesFromJson(version, transactionLogDirectory, fileSystem, DataSize.of(0, BYTE)) .orElseThrow(() -> new TrinoException(DELTA_LAKE_BAD_DATA, "Delta Lake log entries are missing for version " + finalVersion)); } catch (IOException e) { throw new TrinoException(DELTA_LAKE_FILESYSTEM_ERROR, "Failed to access table metadata", e); } - DeltaLakeCommitSummary commitSummary = new DeltaLakeCommitSummary(version, transactionLogEntries); + DeltaLakeCommitSummary commitSummary = new DeltaLakeCommitSummary(version, transactionLogEntries, fileSystem); checkNoMetadataUpdates(commitSummary); checkNoProtocolUpdates(commitSummary); @@ -2442,7 +2432,7 @@ public Optional getUpdateLayout(ConnectorSession se } @Override - public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, RetryMode retryMode) + public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, Map> updateCaseColumns, RetryMode retryMode) { DeltaLakeTableHandle handle = (DeltaLakeTableHandle) tableHandle; if (isAppendOnly(handle.getMetadataEntry(), handle.getProtocolEntry())) { @@ -3582,12 +3572,6 @@ public void validateScan(ConnectorSession session, ConnectorTableHandle handle) } } - @Override - public Optional applyTableScanRedirect(ConnectorSession session, ConnectorTableHandle tableHandle) - { - return deltaLakeRedirectionsProvider.getTableScanRedirection(session, (DeltaLakeTableHandle) tableHandle); - } - @Override public ConnectorAnalyzeMetadata getStatisticsCollectionMetadata(ConnectorSession session, ConnectorTableHandle tableHandle, Map analyzeProperties) { 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 49cfc79e0c52..b238c6c82c4d 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 @@ -17,6 +17,8 @@ import io.airlift.concurrent.BoundedExecutor; import io.airlift.json.JsonCodec; import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.deltalake.metastore.DeltaLakeTableMetadataScheduler; import io.trino.plugin.deltalake.metastore.HiveMetastoreBackedDeltaLakeMetastore; import io.trino.plugin.deltalake.statistics.CachingExtendedStatisticsAccess; @@ -26,8 +28,6 @@ import io.trino.plugin.deltalake.transactionlog.writer.TransactionLogWriterFactory; import io.trino.plugin.hive.NodeVersion; import io.trino.plugin.hive.TrinoViewHiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.security.AccessControlMetadata; import io.trino.spi.NodeManager; import io.trino.spi.security.ConnectorIdentity; @@ -38,7 +38,7 @@ import java.util.concurrent.ExecutorService; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static java.util.Objects.requireNonNull; public class DeltaLakeMetadataFactory @@ -53,7 +53,6 @@ public class DeltaLakeMetadataFactory private final TransactionLogWriterFactory transactionLogWriterFactory; private final NodeManager nodeManager; private final CheckpointWriterManager checkpointWriterManager; - private final DeltaLakeRedirectionsProvider deltaLakeRedirectionsProvider; private final CachingExtendedStatisticsAccess statisticsAccess; private final int domainCompactionThreshold; private final boolean unsafeWritesEnabled; @@ -79,7 +78,6 @@ public DeltaLakeMetadataFactory( TransactionLogWriterFactory transactionLogWriterFactory, NodeManager nodeManager, CheckpointWriterManager checkpointWriterManager, - DeltaLakeRedirectionsProvider deltaLakeRedirectionsProvider, CachingExtendedStatisticsAccess statisticsAccess, @AllowDeltaLakeManagedTableRename boolean allowManagedTableRename, NodeVersion nodeVersion, @@ -96,7 +94,6 @@ public DeltaLakeMetadataFactory( this.transactionLogWriterFactory = requireNonNull(transactionLogWriterFactory, "transactionLogWriterFactory is null"); this.nodeManager = requireNonNull(nodeManager, "nodeManager is null"); this.checkpointWriterManager = requireNonNull(checkpointWriterManager, "checkpointWriterManager is null"); - this.deltaLakeRedirectionsProvider = requireNonNull(deltaLakeRedirectionsProvider, "deltaLakeRedirectionsProvider is null"); this.statisticsAccess = requireNonNull(statisticsAccess, "statisticsAccess is null"); this.domainCompactionThreshold = deltaLakeConfig.getDomainCompactionThreshold(); this.unsafeWritesEnabled = deltaLakeConfig.getUnsafeWritesEnabled(); @@ -148,7 +145,6 @@ public DeltaLakeMetadata create(ConnectorIdentity identity) checkpointWriterManager, checkpointWritingInterval, deleteSchemaLocationsFallback, - deltaLakeRedirectionsProvider, statisticsAccess, metadataScheduler, useUniqueTableLocation, diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeModule.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeModule.java index 7c62bdb87765..a4939050b7bc 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeModule.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeModule.java @@ -127,9 +127,6 @@ public void setup(Binder binder) binder.bind(NoIsolationSynchronizer.class).in(Scopes.SINGLETON); newMapBinder(binder, String.class, TransactionLogSynchronizer.class); - newOptionalBinder(binder, DeltaLakeRedirectionsProvider.class) - .setDefault().toInstance(DeltaLakeRedirectionsProvider.NOOP); - jsonCodecBinder(binder).bindJsonCodec(DataFileInfo.class); jsonCodecBinder(binder).bindJsonCodec(DeltaLakeMergeResult.class); binder.bind(DeltaLakeWriterStats.class).in(Scopes.SINGLETON); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeRedirectionsProvider.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeRedirectionsProvider.java index b371d787b2af..090d91b26ce4 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeRedirectionsProvider.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeRedirectionsProvider.java @@ -20,7 +20,5 @@ public interface DeltaLakeRedirectionsProvider { - DeltaLakeRedirectionsProvider NOOP = (session, tableHandle) -> Optional.empty(); - Optional getTableScanRedirection(ConnectorSession session, DeltaLakeTableHandle tableHandle); } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeTransactionsTable.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeTransactionsTable.java index b35ce6074618..194481f9abe9 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeTransactionsTable.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeTransactionsTable.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableList; import io.airlift.json.JsonCodec; +import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry; import io.trino.plugin.deltalake.transactionlog.Transaction; @@ -62,12 +63,13 @@ public DeltaLakeTransactionsTable( } @Override - protected List buildPages(ConnectorSession session, PageListBuilder pagesBuilder, List transactions) + protected List buildPages(ConnectorSession session, PageListBuilder pagesBuilder, List transactions, TrinoFileSystem fileSystem) { for (Transaction transaction : transactions) { pagesBuilder.beginRow(); pagesBuilder.appendBigint(transaction.transactionId()); - pagesBuilder.appendVarchar(TRANSACTION_LOG_ENTRIES_CODEC.toJson(transaction.transactionEntries())); + pagesBuilder.appendVarchar(TRANSACTION_LOG_ENTRIES_CODEC.toJson( + transaction.transactionEntries().getEntriesList(fileSystem))); pagesBuilder.endRow(); } return pagesBuilder.build(); 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 8f686205e239..c247261db38d 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 @@ -22,7 +22,6 @@ import io.trino.parquet.metadata.BlockMetadata; import io.trino.parquet.metadata.ColumnChunkMetadata; import io.trino.parquet.metadata.ParquetMetadata; -import io.trino.parquet.reader.MetadataReader; import io.trino.plugin.deltalake.DataFileInfo.DataFileType; import io.trino.plugin.deltalake.transactionlog.statistics.DeltaLakeJsonFileStatistics; import io.trino.plugin.hive.FileWriter; @@ -184,7 +183,7 @@ public DataFileInfo getDataFileInfo() { Location path = rootTableLocation.appendPath(relativeFilePath); FileMetaData fileMetaData = fileWriter.getFileMetadata(); - ParquetMetadata parquetMetadata = MetadataReader.createParquetMetadata(fileMetaData, new ParquetDataSourceId(path.toString())); + ParquetMetadata parquetMetadata = new ParquetMetadata(fileMetaData, new ParquetDataSourceId(path.toString())); return new DataFileInfo( relativeFilePath, diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesSplitSource.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesSplitSource.java index 084fe36f0f45..e9056e42ec83 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesSplitSource.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesSplitSource.java @@ -39,6 +39,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.getOnlyElement; +import static io.trino.plugin.deltalake.DeltaLakeConfig.DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE; import static io.trino.plugin.deltalake.DeltaLakeErrorCode.DELTA_LAKE_BAD_DATA; import static io.trino.plugin.deltalake.DeltaLakeErrorCode.DELTA_LAKE_FILESYSTEM_ERROR; import static io.trino.plugin.deltalake.functions.tablechanges.TableChangesFileType.CDF_FILE; @@ -74,11 +75,9 @@ private Stream prepareSplits(long currentVersion, long tableRead .boxed() .flatMap(version -> { try { - List entries = getEntriesFromJson(version, transactionLogDir, fileSystem) - .orElseThrow(() -> new TrinoException(DELTA_LAKE_BAD_DATA, "Delta Lake log entries are missing for version " + version)); - if (entries.isEmpty()) { - return ImmutableList.of().stream(); - } + List entries = getEntriesFromJson(version, transactionLogDir, fileSystem, DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE) + .orElseThrow(() -> new TrinoException(DELTA_LAKE_BAD_DATA, "Delta Lake log entries are missing for version " + version)) + .getEntriesList(fileSystem); List commitInfoEntries = entries.stream() .map(DeltaLakeTransactionLogEntry::getCommitInfo) .filter(Objects::nonNull) 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 4eb4f8fe3ab7..6bfd78808c7a 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 @@ -39,7 +39,7 @@ protected void setup(Binder binder) bindMetastoreModule("glue", new DeltaLakeGlueMetastoreModule()); bindMetastoreModule("glue-v1", new DeltaLakeGlueV1MetastoreModule()); - install(new CachingHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule()); } private void bindMetastoreModule(String name, Module module) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/file/DeltaLakeFileMetastoreTableOperationsProvider.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/file/DeltaLakeFileMetastoreTableOperationsProvider.java index 898679cb6497..5a7ecbdee9ac 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/file/DeltaLakeFileMetastoreTableOperationsProvider.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/file/DeltaLakeFileMetastoreTableOperationsProvider.java @@ -14,9 +14,9 @@ package io.trino.plugin.deltalake.metastore.file; import com.google.inject.Inject; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.deltalake.metastore.DeltaLakeTableOperations; import io.trino.plugin.deltalake.metastore.DeltaLakeTableOperationsProvider; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.connector.ConnectorSession; import java.util.Optional; diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/glue/v1/DeltaLakeGlueMetastoreTableFilterProvider.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/glue/v1/DeltaLakeGlueMetastoreTableFilterProvider.java index f44eb3f1c3be..654b251bbb8f 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/glue/v1/DeltaLakeGlueMetastoreTableFilterProvider.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/glue/v1/DeltaLakeGlueMetastoreTableFilterProvider.java @@ -21,7 +21,7 @@ import java.util.function.Predicate; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; public class DeltaLakeGlueMetastoreTableFilterProvider implements Provider> diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/glue/v1/DeltaLakeGlueV1MetastoreTableOperations.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/glue/v1/DeltaLakeGlueV1MetastoreTableOperations.java index 1e1df8668529..d3e0eed81a98 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/glue/v1/DeltaLakeGlueV1MetastoreTableOperations.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/glue/v1/DeltaLakeGlueV1MetastoreTableOperations.java @@ -28,8 +28,8 @@ import java.util.Optional; import static io.trino.plugin.deltalake.metastore.DeltaLakeTableMetadataScheduler.tableMetadataParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueInputConverter.convertGlueTableToTableInput; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueInputConverter.convertGlueTableToTableInput; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; import static java.util.Objects.requireNonNull; public class DeltaLakeGlueV1MetastoreTableOperations diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/thrift/DeltaLakeThriftMetastoreModule.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/thrift/DeltaLakeThriftMetastoreModule.java index 8946e5ec9c58..0ec996a93eae 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/thrift/DeltaLakeThriftMetastoreModule.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/thrift/DeltaLakeThriftMetastoreModule.java @@ -20,7 +20,6 @@ import io.trino.plugin.deltalake.AllowDeltaLakeManagedTableRename; import io.trino.plugin.deltalake.MaxTableParameterLength; import io.trino.plugin.deltalake.metastore.DeltaLakeTableOperationsProvider; -import io.trino.plugin.deltalake.metastore.file.DeltaLakeFileMetastoreTableOperationsProvider; import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreModule; public class DeltaLakeThriftMetastoreModule @@ -30,7 +29,7 @@ public class DeltaLakeThriftMetastoreModule protected void setup(Binder binder) { install(new ThriftMetastoreModule()); - binder.bind(DeltaLakeTableOperationsProvider.class).to(DeltaLakeFileMetastoreTableOperationsProvider.class).in(Scopes.SINGLETON); + binder.bind(DeltaLakeTableOperationsProvider.class).to(DeltaLakeThriftMetastoreTableOperationsProvider.class).in(Scopes.SINGLETON); binder.bind(Key.get(boolean.class, AllowDeltaLakeManagedTableRename.class)).toInstance(false); // Limit per Hive metastore code (https://github.com/apache/hive/tree/7f6367e0c6e21b11ef62da1ea6681a54d547de07/standalone-metastore/metastore-server/src/main/sql as of this writing) // - MySQL: mediumtext (16777215) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/thrift/DeltaLakeThriftMetastoreTableOperationsProvider.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/thrift/DeltaLakeThriftMetastoreTableOperationsProvider.java index 9ff99a10cc55..d170376601ed 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/thrift/DeltaLakeThriftMetastoreTableOperationsProvider.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/thrift/DeltaLakeThriftMetastoreTableOperationsProvider.java @@ -14,9 +14,9 @@ package io.trino.plugin.deltalake.metastore.thrift; import com.google.inject.Inject; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.deltalake.metastore.DeltaLakeTableOperations; import io.trino.plugin.deltalake.metastore.DeltaLakeTableOperationsProvider; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreFactory; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.security.ConnectorIdentity; diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/FlushMetadataCacheProcedure.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/FlushMetadataCacheProcedure.java index 220636d141f1..97fa7e73c306 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/FlushMetadataCacheProcedure.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/FlushMetadataCacheProcedure.java @@ -16,9 +16,9 @@ import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import com.google.inject.Provider; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.deltalake.statistics.CachingExtendedStatisticsAccess; import io.trino.plugin.deltalake.transactionlog.TransactionLogAccess; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.spi.TrinoException; import io.trino.spi.classloader.ThreadContextClassLoader; import io.trino.spi.connector.SchemaTableName; 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 bc27669c87f7..e84a5d700c29 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 @@ -190,8 +190,7 @@ private void doVacuum( checkUnsupportedUniversalFormat(handle.getMetadataEntry()); - TableSnapshot tableSnapshot = metadata.getSnapshot(session, tableName, handle.getLocation(), Optional.of(handle.getReadVersion())); - ProtocolEntry protocolEntry = transactionLogAccess.getProtocolEntry(session, tableSnapshot); + ProtocolEntry protocolEntry = handle.getProtocolEntry(); if (protocolEntry.minWriterVersion() > MAX_WRITER_VERSION) { throw new TrinoException(NOT_SUPPORTED, "Cannot execute vacuum procedure with %d writer version".formatted(protocolEntry.minWriterVersion())); } @@ -205,6 +204,7 @@ private void doVacuum( throw new TrinoException(NOT_SUPPORTED, "Cannot execute vacuum procedure with %s writer features".formatted(DELETION_VECTORS_FEATURE_NAME)); } + TableSnapshot tableSnapshot = metadata.getSnapshot(session, tableName, handle.getLocation(), Optional.of(handle.getReadVersion())); String tableLocation = tableSnapshot.getTableLocation(); String transactionLogDir = getTransactionLogDir(tableLocation); TrinoFileSystem fileSystem = fileSystemFactory.create(session); @@ -222,24 +222,26 @@ private void doVacuum( handle.getProtocolEntry(), TupleDomain.all(), alwaysFalse())) { - retainedPaths = Stream.concat( - activeAddEntries - // paths can be absolute as well in case of shallow-cloned tables, and they shouldn't be deleted as part of vacuum because according to - // delta-protocol absolute paths are inherited from base table and the vacuum procedure should only list and delete local file references - .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::path)) - .peek(path -> checkState(!path.startsWith(tableLocation), "Unexpected absolute path in transaction log: %s", path)) - .collect(toImmutableSet()); + try (Stream pathEntries = Stream.concat( + activeAddEntries + // paths can be absolute as well in case of shallow-cloned tables, and they shouldn't be deleted as part of vacuum because according to + // delta-protocol absolute paths are inherited from base table and the vacuum procedure should only list and delete local file references + .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::path))) { + retainedPaths = pathEntries + .peek(path -> checkState(!path.startsWith(tableLocation), "Unexpected absolute path in transaction log: %s", path)) + .collect(toImmutableSet()); + } } log.debug( diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/DeletionVectorEntry.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/DeletionVectorEntry.java index cf23159250f1..b1c0c2805787 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/DeletionVectorEntry.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/DeletionVectorEntry.java @@ -34,6 +34,16 @@ public record DeletionVectorEntry(String storageType, String pathOrInlineDv, Opt requireNonNull(offset, "offset is null"); } + // https://github.com/delta-io/delta/blob/34f02d8/kernel/kernel-api/src/main/java/io/delta/kernel/internal/actions/DeletionVectorDescriptor.java#L167-L174 + public String uniqueId() + { + String uniqueFileId = storageType + pathOrInlineDv; + if (offset.isPresent()) { + return uniqueFileId + "@" + offset; + } + return uniqueFileId; + } + public long getRetainedSizeInBytes() { return INSTANCE_SIZE 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 203861cfe011..16e5bd1d29ee 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 @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import io.airlift.units.DataSize; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoInputFile; @@ -70,10 +71,12 @@ public class TableSnapshot private final TransactionLogTail logTail; private final String tableLocation; private final ParquetReaderOptions parquetReaderOptions; + private final DataSize transactionLogMaxCachedFileSize; private final boolean checkpointRowStatisticsWritingEnabled; private final int domainCompactionThreshold; private Optional cachedMetadata = Optional.empty(); + private Optional cachedProtocol = Optional.empty(); private TableSnapshot( SchemaTableName table, @@ -82,7 +85,8 @@ private TableSnapshot( String tableLocation, ParquetReaderOptions parquetReaderOptions, boolean checkpointRowStatisticsWritingEnabled, - int domainCompactionThreshold) + int domainCompactionThreshold, + DataSize transactionLogMaxCachedFileSize) { this.table = requireNonNull(table, "table is null"); this.lastCheckpoint = requireNonNull(lastCheckpoint, "lastCheckpoint is null"); @@ -91,6 +95,7 @@ private TableSnapshot( this.parquetReaderOptions = requireNonNull(parquetReaderOptions, "parquetReaderOptions is null"); this.checkpointRowStatisticsWritingEnabled = checkpointRowStatisticsWritingEnabled; this.domainCompactionThreshold = domainCompactionThreshold; + this.transactionLogMaxCachedFileSize = requireNonNull(transactionLogMaxCachedFileSize, "transactionLogMaxCachedFileSize is null"); } public static TableSnapshot load( @@ -101,11 +106,12 @@ public static TableSnapshot load( ParquetReaderOptions parquetReaderOptions, boolean checkpointRowStatisticsWritingEnabled, int domainCompactionThreshold, + DataSize transactionLogMaxCachedFileSize, Optional endVersion) throws IOException { Optional lastCheckpointVersion = lastCheckpoint.map(LastCheckpoint::version); - TransactionLogTail transactionLogTail = TransactionLogTail.loadNewTail(fileSystem, tableLocation, lastCheckpointVersion, endVersion); + TransactionLogTail transactionLogTail = TransactionLogTail.loadNewTail(fileSystem, tableLocation, lastCheckpointVersion, endVersion, transactionLogMaxCachedFileSize); return new TableSnapshot( table, @@ -114,7 +120,8 @@ public static TableSnapshot load( tableLocation, parquetReaderOptions, checkpointRowStatisticsWritingEnabled, - domainCompactionThreshold); + domainCompactionThreshold, + transactionLogMaxCachedFileSize); } public Optional getUpdatedSnapshot(TrinoFileSystem fileSystem, Optional toVersion) @@ -136,12 +143,13 @@ public Optional getUpdatedSnapshot(TrinoFileSystem fileSystem, Op parquetReaderOptions, checkpointRowStatisticsWritingEnabled, domainCompactionThreshold, + transactionLogMaxCachedFileSize, Optional.empty())); } } } - Optional updatedLogTail = logTail.getUpdatedTail(fileSystem, tableLocation, toVersion); + Optional updatedLogTail = logTail.getUpdatedTail(fileSystem, tableLocation, toVersion, transactionLogMaxCachedFileSize); return updatedLogTail.map(transactionLogTail -> new TableSnapshot( table, lastCheckpoint, @@ -149,7 +157,8 @@ public Optional getUpdatedSnapshot(TrinoFileSystem fileSystem, Op tableLocation, parquetReaderOptions, checkpointRowStatisticsWritingEnabled, - domainCompactionThreshold)); + domainCompactionThreshold, + transactionLogMaxCachedFileSize)); } public long getVersion() @@ -167,6 +176,11 @@ public Optional getCachedMetadata() return cachedMetadata; } + public Optional getCachedProtocol() + { + return cachedProtocol; + } + public String getTableLocation() { return tableLocation; @@ -177,9 +191,14 @@ public void setCachedMetadata(Optional cachedMetadata) this.cachedMetadata = cachedMetadata; } - public List getJsonTransactionLogEntries() + public void setCachedProtocol(Optional cachedProtocol) + { + this.cachedProtocol = cachedProtocol; + } + + public List getJsonTransactionLogEntries(TrinoFileSystem fileSystem) { - return logTail.getFileEntries(); + return logTail.getFileEntries(fileSystem); } public List getTransactions() @@ -194,7 +213,8 @@ public long getRetainedSizeInBytes() + table.getRetainedSizeInBytes() + logTail.getRetainedSizeInBytes() + estimatedSizeOf(tableLocation) - + sizeOf(cachedMetadata, MetadataEntry::getRetainedSizeInBytes); + + sizeOf(cachedMetadata, MetadataEntry::getRetainedSizeInBytes) + + sizeOf(cachedProtocol, ProtocolEntry::getRetainedSizeInBytes); } public Stream getCheckpointTransactionLogEntries( @@ -360,7 +380,9 @@ private Stream getV2CheckpointEntries( { if (checkpointFile.location().fileName().endsWith(".json")) { try { - return getEntriesFromJson(checkpoint.version(), checkpointFile).stream().flatMap(List::stream); + return getEntriesFromJson(checkpoint.version(), checkpointFile, transactionLogMaxCachedFileSize) + .stream() + .flatMap(logEntries -> logEntries.getEntries(fileSystem)); } catch (IOException e) { throw new TrinoException(DELTA_LAKE_FILESYSTEM_ERROR, format("Unexpected IO exception occurred while reading the entries of the file: %s for the table %s", checkpoint, table), e); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/Transaction.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/Transaction.java index ebfa2b305d4f..d5c3b0b8dcf9 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/Transaction.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/Transaction.java @@ -13,30 +13,25 @@ */ package io.trino.plugin.deltalake.transactionlog; -import com.google.common.collect.ImmutableList; - -import java.util.List; - import static com.google.common.base.Preconditions.checkArgument; import static io.airlift.slice.SizeOf.SIZE_OF_LONG; -import static io.airlift.slice.SizeOf.estimatedSizeOf; import static io.airlift.slice.SizeOf.instanceSize; import static java.util.Objects.requireNonNull; -public record Transaction(long transactionId, List transactionEntries) +public record Transaction(long transactionId, TransactionLogEntries transactionEntries) { private static final int INSTANCE_SIZE = instanceSize(Transaction.class); public Transaction { checkArgument(transactionId >= 0, "transactionId must be >= 0"); - transactionEntries = ImmutableList.copyOf(requireNonNull(transactionEntries, "transactionEntries is null")); + requireNonNull(transactionEntries, "transactionEntries is null"); } public long getRetainedSizeInBytes() { return INSTANCE_SIZE + SIZE_OF_LONG - + estimatedSizeOf(transactionEntries, DeltaLakeTransactionLogEntry::getRetainedSizeInBytes); + + transactionEntries.getRetainedSizeInBytes(); } } 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 27ecdbe7cb58..19a730e9efe8 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 @@ -20,6 +20,7 @@ import com.google.common.primitives.Ints; import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.inject.Inject; +import io.airlift.units.DataSize; import io.trino.cache.CacheStatsMBean; import io.trino.cache.EvictableCacheBuilder; import io.trino.filesystem.FileEntry; @@ -37,6 +38,7 @@ import io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator; import io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointSchemaManager; import io.trino.plugin.deltalake.transactionlog.checkpoint.LastCheckpoint; +import io.trino.plugin.deltalake.transactionlog.checkpoint.MetadataAndProtocolEntries; import io.trino.plugin.deltalake.transactionlog.checkpoint.TransactionLogTail; import io.trino.plugin.hive.parquet.ParquetReaderConfig; import io.trino.spi.TrinoException; @@ -56,7 +58,6 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.time.Instant; -import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashMap; @@ -92,7 +93,6 @@ import static io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator.EntryType.ADD; 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.deltalake.transactionlog.checkpoint.CheckpointEntryIterator.EntryType.REMOVE; import static io.trino.plugin.deltalake.transactionlog.checkpoint.TransactionLogTail.getEntriesFromJson; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -110,6 +110,7 @@ public class TransactionLogAccess private final ParquetReaderOptions parquetReaderOptions; private final boolean checkpointRowStatisticsWritingEnabled; private final int domainCompactionThreshold; + private final DataSize transactionLogMaxCachedFileSize; private final Cache tableSnapshots; private final Cache activeDataFileCache; @@ -130,6 +131,7 @@ public TransactionLogAccess( this.parquetReaderOptions = parquetReaderConfig.toParquetReaderOptions().withBloomFilter(false); this.checkpointRowStatisticsWritingEnabled = deltaLakeConfig.isCheckpointRowStatisticsWritingEnabled(); this.domainCompactionThreshold = deltaLakeConfig.getDomainCompactionThreshold(); + this.transactionLogMaxCachedFileSize = deltaLakeConfig.getTransactionLogMaxCachedFileSize(); tableSnapshots = EvictableCacheBuilder.newBuilder() .weigher((Weigher) (key, value) -> Ints.saturatedCast(key.getRetainedSizeInBytes() + value.getRetainedSizeInBytes())) @@ -184,6 +186,7 @@ public TableSnapshot loadSnapshot(ConnectorSession session, SchemaTableName tabl parquetReaderOptions, checkpointRowStatisticsWritingEnabled, domainCompactionThreshold, + transactionLogMaxCachedFileSize, endVersion)); } catch (UncheckedExecutionException | ExecutionException e) { @@ -215,6 +218,7 @@ private TableSnapshot loadSnapshotForTimeTravel(TrinoFileSystem fileSystem, Sche parquetReaderOptions, checkpointRowStatisticsWritingEnabled, domainCompactionThreshold, + transactionLogMaxCachedFileSize, Optional.of(endVersion)); } @@ -299,12 +303,21 @@ public void invalidateCache(SchemaTableName schemaTableName, Optional ta public MetadataEntry getMetadataEntry(ConnectorSession session, TableSnapshot tableSnapshot) { if (tableSnapshot.getCachedMetadata().isEmpty()) { + TrinoFileSystem fileSystem = fileSystemFactory.create(session); try (Stream metadataEntries = getEntries( session, tableSnapshot, - METADATA, - entryStream -> entryStream.map(DeltaLakeTransactionLogEntry::getMetaData).filter(Objects::nonNull), - fileSystemFactory.create(session), + ImmutableSet.of(METADATA), + (checkpointStream, jsonTransactions) -> + Stream.concat( + checkpointStream + .map(DeltaLakeTransactionLogEntry::getMetaData) + .filter(Objects::nonNull), + jsonTransactions.stream() + .map(transaction -> transaction.transactionEntries().getMetadataAndProtocol(fileSystem)) + .filter(entry -> entry.metadata().isPresent()) + .map(entry -> entry.metadata().get())), + fileSystem, fileFormatDataSourceStats)) { // Get last entry in the stream tableSnapshot.setCachedMetadata(metadataEntries.reduce((first, second) -> second)); @@ -391,17 +404,18 @@ public Stream loadActiveFiles( Predicate addStatsMinMaxColumnFilter) { List transactions = tableSnapshot.getTransactions(); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); try (Stream checkpointEntries = tableSnapshot.getCheckpointTransactionLogEntries( session, ImmutableSet.of(ADD), checkpointSchemaManager, typeManager, - fileSystemFactory.create(session), + fileSystem, fileFormatDataSourceStats, Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)), partitionConstraint, Optional.of(addStatsMinMaxColumnFilter))) { - return activeAddEntries(checkpointEntries, transactions) + return activeAddEntries(checkpointEntries, transactions, fileSystem) .filter(partitionConstraint.isAll() ? addAction -> true : addAction -> partitionMatchesPredicate(addAction.getCanonicalPartitionValues(), partitionConstraint.getDomains().orElseThrow())); @@ -427,25 +441,29 @@ public static ImmutableList columnsWithStats(List activeAddEntries(Stream checkpointEntries, List transactions) + private Stream activeAddEntries(Stream checkpointEntries, List transactions, TrinoFileSystem fileSystem) { - Map activeJsonEntries = new LinkedHashMap<>(); - HashSet removedFiles = new HashSet<>(); + Map activeJsonEntries = new LinkedHashMap<>(); + HashSet removedFiles = new HashSet<>(); // The json entries containing the last few entries in the log need to be applied on top of the parquet snapshot: // - Any files which have been removed need to be excluded // - Any files with newer add actions need to be updated with the most recent metadata transactions.forEach(transaction -> { - Map addFilesInTransaction = new LinkedHashMap<>(); - Set removedFilesInTransaction = new HashSet<>(); - transaction.transactionEntries().forEach(deltaLakeTransactionLogEntry -> { - if (deltaLakeTransactionLogEntry.getAdd() != null) { - addFilesInTransaction.put(deltaLakeTransactionLogEntry.getAdd().getPath(), deltaLakeTransactionLogEntry.getAdd()); - } - else if (deltaLakeTransactionLogEntry.getRemove() != null) { - removedFilesInTransaction.add(deltaLakeTransactionLogEntry.getRemove().path()); - } - }); + Map addFilesInTransaction = new LinkedHashMap<>(); + Set removedFilesInTransaction = new HashSet<>(); + try (Stream entries = transaction.transactionEntries().getEntries(fileSystem)) { + entries.forEach(deltaLakeTransactionLogEntry -> { + if (deltaLakeTransactionLogEntry.getAdd() != null) { + AddFileEntry add = deltaLakeTransactionLogEntry.getAdd(); + addFilesInTransaction.put(new FileEntryKey(add.getPath(), add.getDeletionVector().map(DeletionVectorEntry::uniqueId)), add); + } + else if (deltaLakeTransactionLogEntry.getRemove() != null) { + RemoveFileEntry remove = deltaLakeTransactionLogEntry.getRemove(); + removedFilesInTransaction.add(new FileEntryKey(remove.path(), remove.deletionVector().map(DeletionVectorEntry::uniqueId))); + } + }); + } // Process 'remove' entries first because deletion vectors register both 'add' and 'remove' entries and the 'add' entry should be kept removedFiles.addAll(removedFilesInTransaction); @@ -456,55 +474,72 @@ else if (deltaLakeTransactionLogEntry.getRemove() != null) { Stream filteredCheckpointEntries = checkpointEntries .map(DeltaLakeTransactionLogEntry::getAdd) .filter(Objects::nonNull) - .filter(addEntry -> !removedFiles.contains(addEntry.getPath()) && !activeJsonEntries.containsKey(addEntry.getPath())); + .filter(addEntry -> { + FileEntryKey key = new FileEntryKey(addEntry.getPath(), addEntry.getDeletionVector().map(DeletionVectorEntry::uniqueId)); + return !removedFiles.contains(key) && !activeJsonEntries.containsKey(key); + }); return Stream.concat(filteredCheckpointEntries, activeJsonEntries.values().stream()); } - public Stream getRemoveEntries(ConnectorSession session, TableSnapshot tableSnapshot) - { - return getEntries( - session, - tableSnapshot, - REMOVE, - entryStream -> entryStream.map(DeltaLakeTransactionLogEntry::getRemove).filter(Objects::nonNull), - fileSystemFactory.create(session), - fileFormatDataSourceStats); - } + private record FileEntryKey(String path, Optional deletionVectorId) {} - public Map, Object> getTransactionLogEntries( - ConnectorSession session, - TableSnapshot tableSnapshot, - Set entryTypes, - Function, Stream> entryMapper) + public MetadataAndProtocolEntries getMetadataAndProtocolEntry(ConnectorSession session, TableSnapshot tableSnapshot) { - try (Stream entries = getEntries( - session, - tableSnapshot, - entryTypes, - (checkpointStream, jsonStream) -> entryMapper.apply(Stream.concat(checkpointStream, jsonStream.stream().map(Transaction::transactionEntries).flatMap(Collection::stream))), - fileSystemFactory.create(session), - fileFormatDataSourceStats)) { - return entries.collect(toImmutableMap(Object::getClass, Function.identity(), (first, second) -> second)); + if (tableSnapshot.getCachedMetadata().isEmpty() || tableSnapshot.getCachedProtocol().isEmpty()) { + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + try (Stream entries = getEntries( + session, + tableSnapshot, + ImmutableSet.of(METADATA, PROTOCOL), + (checkpointStream, jsonTransactions) -> + Stream.concat( + checkpointStream + .filter(entry -> entry.getMetaData() != null || entry.getProtocol() != null) + .map(entry -> new MetadataAndProtocolEntries(entry.getMetaData(), entry.getProtocol())), + jsonTransactions.stream() + .map(transaction -> transaction.transactionEntries().getMetadataAndProtocol(fileSystem))), + fileSystem, + fileFormatDataSourceStats)) { + Map, Object> logEntries = entries + .flatMap(MetadataAndProtocolEntries::stream) + .collect(toImmutableMap(Object::getClass, Function.identity(), (_, second) -> second)); + tableSnapshot.setCachedMetadata(Optional.ofNullable((MetadataEntry) logEntries.get(MetadataEntry.class))); + tableSnapshot.setCachedProtocol(Optional.ofNullable((ProtocolEntry) logEntries.get(ProtocolEntry.class))); + } } + return new MetadataAndProtocolEntries(tableSnapshot.getCachedMetadata(), tableSnapshot.getCachedProtocol()); } public ProtocolEntry getProtocolEntry(ConnectorSession session, TableSnapshot tableSnapshot) { - try (Stream protocolEntries = getProtocolEntries(session, tableSnapshot)) { - return protocolEntries.reduce((first, second) -> second) - .orElseThrow(() -> new TrinoException(DELTA_LAKE_INVALID_SCHEMA, "Protocol entry not found in transaction log for table " + tableSnapshot.getTable())); + if (tableSnapshot.getCachedProtocol().isEmpty()) { + try (Stream protocolEntries = getProtocolEntries(session, tableSnapshot)) { + // Get last entry in the stream + tableSnapshot.setCachedProtocol(protocolEntries.reduce((first, second) -> second)); + } } + return tableSnapshot.getCachedProtocol() + .orElseThrow(() -> new TrinoException(DELTA_LAKE_INVALID_SCHEMA, "Protocol entry not found in transaction log for table " + tableSnapshot.getTable())); } public Stream getProtocolEntries(ConnectorSession session, TableSnapshot tableSnapshot) { + TrinoFileSystem fileSystem = fileSystemFactory.create(session); return getEntries( session, tableSnapshot, - PROTOCOL, - entryStream -> entryStream.map(DeltaLakeTransactionLogEntry::getProtocol).filter(Objects::nonNull), - fileSystemFactory.create(session), + ImmutableSet.of(PROTOCOL), + (checkpointStream, jsonTransactions) -> + Stream.concat( + checkpointStream + .map(DeltaLakeTransactionLogEntry::getProtocol) + .filter(Objects::nonNull), + jsonTransactions.stream() + .map(transaction -> transaction.transactionEntries().getMetadataAndProtocol(fileSystem)) + .filter(entry -> entry.protocol().isPresent()) + .map(entry -> entry.protocol().get())), + fileSystem, fileFormatDataSourceStats); } @@ -543,34 +578,13 @@ private Stream getEntries( } } - /** - * Convenience method for accessors which don't need to separate out the checkpoint entries from the json entries. - */ - private Stream getEntries( - ConnectorSession session, - TableSnapshot tableSnapshot, - CheckpointEntryIterator.EntryType entryType, - Function, Stream> entryMapper, - TrinoFileSystem fileSystem, - FileFormatDataSourceStats stats) - { - return getEntries( - session, - tableSnapshot, - ImmutableSet.of(entryType), - (checkpointStream, jsonStream) -> entryMapper.apply(Stream.concat(checkpointStream, jsonStream.stream().map(Transaction::transactionEntries).flatMap(Collection::stream))), - fileSystem, - stats); - } - public Stream getJsonEntries(TrinoFileSystem fileSystem, String transactionLogDir, List forVersions) { return forVersions.stream() .flatMap(version -> { try { - Optional> entriesFromJson = getEntriesFromJson(version, transactionLogDir, fileSystem); - //noinspection SimplifyOptionalCallChains - return entriesFromJson.map(List::stream) + Optional entriesFromJson = getEntriesFromJson(version, transactionLogDir, fileSystem, transactionLogMaxCachedFileSize); + return entriesFromJson.map(entries -> entries.getEntries(fileSystem)) // transaction log does not exist. Might have been expired. .orElseGet(Stream::of); } @@ -607,17 +621,17 @@ public List getPastTableVersions(TrinoFileSystem fileSystem, String transa return result.build(); } - private static List getJsonEntries(long startVersion, long endVersion, TableSnapshot tableSnapshot, TrinoFileSystem fileSystem) + private List getJsonEntries(long startVersion, long endVersion, TableSnapshot tableSnapshot, TrinoFileSystem fileSystem) throws IOException { Optional lastCheckpointVersion = tableSnapshot.getLastCheckpointVersion(); if (lastCheckpointVersion.isPresent() && startVersion < lastCheckpointVersion.get()) { return ImmutableList.builder() - .addAll(TransactionLogTail.loadNewTail(fileSystem, tableSnapshot.getTableLocation(), Optional.of(startVersion), lastCheckpointVersion).getFileEntries()) - .addAll(tableSnapshot.getJsonTransactionLogEntries()) + .addAll(TransactionLogTail.loadNewTail(fileSystem, tableSnapshot.getTableLocation(), Optional.of(startVersion), lastCheckpointVersion, transactionLogMaxCachedFileSize).getFileEntries(fileSystem)) + .addAll(tableSnapshot.getJsonTransactionLogEntries(fileSystem)) .build(); } - return TransactionLogTail.loadNewTail(fileSystem, tableSnapshot.getTableLocation(), Optional.of(startVersion), Optional.of(endVersion)).getFileEntries(); + return TransactionLogTail.loadNewTail(fileSystem, tableSnapshot.getTableLocation(), Optional.of(startVersion), Optional.of(endVersion), transactionLogMaxCachedFileSize).getFileEntries(fileSystem); } public static String canonicalizeColumnName(String columnName) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogEntries.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogEntries.java new file mode 100644 index 000000000000..55038dbe9c52 --- /dev/null +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogEntries.java @@ -0,0 +1,176 @@ +/* + * 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; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableList; +import io.airlift.units.DataSize; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoInputFile; +import io.trino.filesystem.TrinoInputStream; +import io.trino.plugin.deltalake.transactionlog.checkpoint.MetadataAndProtocolEntries; +import io.trino.spi.TrinoException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Streams.stream; +import static io.airlift.slice.SizeOf.SIZE_OF_LONG; +import static io.airlift.slice.SizeOf.estimatedSizeOf; +import static io.airlift.slice.SizeOf.instanceSize; +import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.plugin.deltalake.transactionlog.TransactionLogParser.parseJson; +import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; +import static java.nio.charset.StandardCharsets.UTF_8; + +public final class TransactionLogEntries +{ + private static final int INSTANCE_SIZE = instanceSize(TransactionLogEntries.class); + private static final int JSON_LOG_ENTRY_READ_BUFFER_SIZE = 1024 * 1024; + + private final long entryNumber; + private final Location transactionLogFilePath; + + private final Optional> cachedEntries; + + public TransactionLogEntries(long entryNumber, TrinoInputFile inputFile, DataSize maxCachedFileSize) + { + this.entryNumber = entryNumber; + this.transactionLogFilePath = inputFile.location(); + try { + if (inputFile.length() > maxCachedFileSize.toBytes()) { + this.cachedEntries = Optional.empty(); + } + else { + this.cachedEntries = Optional.of(ImmutableList.copyOf(new TransactionLogEntryIterator(entryNumber, inputFile))); + } + } + catch (IOException e) { + throw new TrinoException(GENERIC_INTERNAL_ERROR, "Error while reading from transaction entry iterator for the file %s".formatted(transactionLogFilePath)); + } + } + + /** + * Returns a stream of DeltaLakeTransactionLogEntry + * Caller has the responsibility to close this stream as it potentially holds an open file + */ + public Stream getEntries(TrinoFileSystem fileSystem) + { + if (cachedEntries.isPresent()) { + return cachedEntries.get().stream(); + } + TransactionLogEntryIterator iterator = new TransactionLogEntryIterator(entryNumber, fileSystem.newInputFile(transactionLogFilePath)); + return stream(iterator).onClose(iterator::close); + } + + public List getEntriesList(TrinoFileSystem fileSystem) + { + try (Stream jsonStream = getEntries(fileSystem)) { + return jsonStream.collect(toImmutableList()); + } + } + + public MetadataAndProtocolEntries getMetadataAndProtocol(TrinoFileSystem fileSystem) + { + // There can be at most one metadata and protocol entry per transaction log + // We use that stop reading from file when a metadata and protocol entry are found + try (Stream logEntryStream = getEntries(fileSystem)) { + Optional metadataEntry = Optional.empty(); + Optional protocolEntry = Optional.empty(); + for (Iterator it = logEntryStream.iterator(); it.hasNext(); ) { + DeltaLakeTransactionLogEntry transactionLogEntry = it.next(); + if (transactionLogEntry.getMetaData() != null) { + metadataEntry = Optional.of(transactionLogEntry.getMetaData()); + } + else if (transactionLogEntry.getProtocol() != null) { + protocolEntry = Optional.of(transactionLogEntry.getProtocol()); + } + + if (protocolEntry.isPresent() && metadataEntry.isPresent()) { + break; + } + } + return new MetadataAndProtocolEntries(metadataEntry, protocolEntry); + } + } + + public long getRetainedSizeInBytes() + { + return INSTANCE_SIZE + + SIZE_OF_LONG + + estimatedSizeOf(transactionLogFilePath.path()) + + sizeOf(cachedEntries, entries -> estimatedSizeOf(entries, DeltaLakeTransactionLogEntry::getRetainedSizeInBytes)); + } + + private static final class TransactionLogEntryIterator + extends AbstractIterator + { + private final long entryNumber; + private final Location location; + private final BufferedReader reader; + + public TransactionLogEntryIterator(long entryNumber, TrinoInputFile inputFile) + { + this.entryNumber = entryNumber; + this.location = inputFile.location(); + TrinoInputStream inputStream; + try { + inputStream = inputFile.newStream(); + } + catch (Exception e) { + throw new TrinoException(GENERIC_INTERNAL_ERROR, "Error while initializing the transaction entry iterator for the file %s".formatted(inputFile.location())); + } + this.reader = new BufferedReader(new InputStreamReader(inputStream, UTF_8), JSON_LOG_ENTRY_READ_BUFFER_SIZE); + } + + @Override + protected DeltaLakeTransactionLogEntry computeNext() + { + String line; + try { + line = reader.readLine(); + } + catch (IOException e) { + throw new TrinoException(GENERIC_INTERNAL_ERROR, "Error while reading from transaction entry iterator for the file %s".formatted(location)); + } + if (line == null) { + close(); + return endOfData(); + } + DeltaLakeTransactionLogEntry deltaLakeTransactionLogEntry = parseJson(line); + if (deltaLakeTransactionLogEntry.getCommitInfo() != null && deltaLakeTransactionLogEntry.getCommitInfo().version() == 0L) { + // In case that the commit info version is missing, use the version from the transaction log file name + deltaLakeTransactionLogEntry = deltaLakeTransactionLogEntry.withCommitInfo(deltaLakeTransactionLogEntry.getCommitInfo().withVersion(entryNumber)); + } + return deltaLakeTransactionLogEntry; + } + + public void close() + { + try { + reader.close(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} 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 5403ad33628d..a1d9050047f6 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 @@ -147,7 +147,7 @@ public void writeCheckpoint(ConnectorSession session, TableSnapshot snapshot) } } - snapshot.getJsonTransactionLogEntries() + snapshot.getJsonTransactionLogEntries(fileSystem) .forEach(checkpointBuilder::addLogEntry); Location transactionLogDir = Location.of(getTransactionLogDir(snapshot.getTableLocation())); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/MetadataAndProtocolEntries.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/MetadataAndProtocolEntries.java new file mode 100644 index 000000000000..d4f1fdbd743f --- /dev/null +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/MetadataAndProtocolEntries.java @@ -0,0 +1,54 @@ +/* + * 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 io.trino.plugin.deltalake.transactionlog.MetadataEntry; +import io.trino.plugin.deltalake.transactionlog.ProtocolEntry; + +import java.util.Optional; +import java.util.stream.Stream; + +import static io.airlift.slice.SizeOf.instanceSize; +import static io.airlift.slice.SizeOf.sizeOf; + +public record MetadataAndProtocolEntries(Optional metadata, Optional protocol) +{ + private static final int INSTANCE_SIZE = instanceSize(MetadataAndProtocolEntries.class); + + public MetadataAndProtocolEntries(MetadataEntry metadata, ProtocolEntry protocol) + { + this(Optional.ofNullable(metadata), Optional.ofNullable(protocol)); + } + + public Stream stream() + { + if (metadata.isPresent() && protocol.isPresent()) { + return Stream.of(metadata.get(), protocol.get()); + } + if (metadata.isPresent()) { + return Stream.of(metadata.get()); + } + if (protocol.isPresent()) { + return Stream.of(protocol.get()); + } + return Stream.of(); + } + + public long getRetainedSizeInBytes() + { + return INSTANCE_SIZE + + sizeOf(metadata, MetadataEntry::getRetainedSizeInBytes) + + sizeOf(protocol, ProtocolEntry::getRetainedSizeInBytes); + } +} diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TransactionLogTail.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TransactionLogTail.java index ec3e8c3b973a..6f63a9efa013 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TransactionLogTail.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TransactionLogTail.java @@ -14,18 +14,17 @@ package io.trino.plugin.deltalake.transactionlog.checkpoint; import com.google.common.collect.ImmutableList; +import io.airlift.units.DataSize; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoInputFile; import io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry; import io.trino.plugin.deltalake.transactionlog.MissingTransactionLogException; import io.trino.plugin.deltalake.transactionlog.Transaction; +import io.trino.plugin.deltalake.transactionlog.TransactionLogEntries; -import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStreamReader; -import java.util.Collection; import java.util.List; import java.util.Optional; @@ -34,16 +33,13 @@ import static io.airlift.slice.SizeOf.SIZE_OF_LONG; import static io.airlift.slice.SizeOf.estimatedSizeOf; import static io.airlift.slice.SizeOf.instanceSize; -import static io.trino.plugin.deltalake.transactionlog.TransactionLogParser.parseJson; import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.getTransactionLogDir; import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.getTransactionLogJsonEntryPath; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; public class TransactionLogTail { private static final int INSTANCE_SIZE = instanceSize(TransactionLogTail.class); - private static final int JSON_LOG_ENTRY_READ_BUFFER_SIZE = 1024 * 1024; private final List entries; private final long version; @@ -59,7 +55,8 @@ public static TransactionLogTail loadNewTail( TrinoFileSystem fileSystem, String tableLocation, Optional startVersion, - Optional endVersion) + Optional endVersion, + DataSize transactionLogMaxCachedFileSize) throws IOException { ImmutableList.Builder entriesBuilder = ImmutableList.builder(); @@ -74,11 +71,10 @@ public static TransactionLogTail loadNewTail( checkArgument(endVersion.isEmpty() || entryNumber <= endVersion.get(), "Invalid start/end versions: %s, %s", startVersion, endVersion); String transactionLogDir = getTransactionLogDir(tableLocation); - Optional> results; boolean endOfTail = false; while (!endOfTail) { - results = getEntriesFromJson(entryNumber, transactionLogDir, fileSystem); + Optional results = getEntriesFromJson(entryNumber, transactionLogDir, fileSystem, transactionLogMaxCachedFileSize); if (results.isPresent()) { entriesBuilder.add(new Transaction(entryNumber, results.get())); version = entryNumber; @@ -99,11 +95,11 @@ public static TransactionLogTail loadNewTail( return new TransactionLogTail(entriesBuilder.build(), version); } - public Optional getUpdatedTail(TrinoFileSystem fileSystem, String tableLocation, Optional endVersion) + public Optional getUpdatedTail(TrinoFileSystem fileSystem, String tableLocation, Optional endVersion, DataSize transactionLogMaxCachedFileSize) throws IOException { checkArgument(endVersion.isEmpty() || endVersion.get() > version, "Invalid endVersion, expected higher than %s, but got %s", version, endVersion); - TransactionLogTail newTail = loadNewTail(fileSystem, tableLocation, Optional.of(version), endVersion); + TransactionLogTail newTail = loadNewTail(fileSystem, tableLocation, Optional.of(version), endVersion, transactionLogMaxCachedFileSize); if (newTail.version == version) { return Optional.empty(); } @@ -115,42 +111,32 @@ public Optional getUpdatedTail(TrinoFileSystem fileSystem, S newTail.version)); } - public static Optional> getEntriesFromJson(long entryNumber, String transactionLogDir, TrinoFileSystem fileSystem) + public static Optional getEntriesFromJson(long entryNumber, String transactionLogDir, TrinoFileSystem fileSystem, DataSize transactionLogMaxCachedFileSize) throws IOException { Location transactionLogFilePath = getTransactionLogJsonEntryPath(transactionLogDir, entryNumber); TrinoInputFile inputFile = fileSystem.newInputFile(transactionLogFilePath); - return getEntriesFromJson(entryNumber, inputFile); + return getEntriesFromJson(entryNumber, inputFile, transactionLogMaxCachedFileSize); } - public static Optional> getEntriesFromJson(long entryNumber, TrinoInputFile inputFile) + public static Optional getEntriesFromJson(long entryNumber, TrinoInputFile inputFile, DataSize transactionLogMaxCachedFileSize) throws IOException { - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(inputFile.newStream(), UTF_8), - JSON_LOG_ENTRY_READ_BUFFER_SIZE)) { - ImmutableList.Builder resultsBuilder = ImmutableList.builder(); - String line = reader.readLine(); - while (line != null) { - DeltaLakeTransactionLogEntry deltaLakeTransactionLogEntry = parseJson(line); - if (deltaLakeTransactionLogEntry.getCommitInfo() != null && deltaLakeTransactionLogEntry.getCommitInfo().version() == 0L) { - // In case that the commit info version is missing, use the version from the transaction log file name - deltaLakeTransactionLogEntry = deltaLakeTransactionLogEntry.withCommitInfo(deltaLakeTransactionLogEntry.getCommitInfo().withVersion(entryNumber)); - } - resultsBuilder.add(deltaLakeTransactionLogEntry); - line = reader.readLine(); - } - - return Optional.of(resultsBuilder.build()); + try { + inputFile.length(); // File length is cached and used in TransactionLogEntries } catch (FileNotFoundException e) { return Optional.empty(); // end of tail } + return Optional.of(new TransactionLogEntries(entryNumber, inputFile, transactionLogMaxCachedFileSize)); } - public List getFileEntries() + public List getFileEntries(TrinoFileSystem fileSystem) { - return entries.stream().map(Transaction::transactionEntries).flatMap(Collection::stream).collect(toImmutableList()); + return entries.stream() + .map(Transaction::transactionEntries) + .flatMap(logEntries -> logEntries.getEntriesList(fileSystem).stream()) + .collect(toImmutableList()); } public List getTransactions() diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/util/DeltaLakeWriteUtils.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/util/DeltaLakeWriteUtils.java index a30846588cf2..33c6f1997d89 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/util/DeltaLakeWriteUtils.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/util/DeltaLakeWriteUtils.java @@ -32,8 +32,8 @@ import java.util.List; import static com.google.common.io.BaseEncoding.base16; +import static io.trino.metastore.Partitions.HIVE_DEFAULT_DYNAMIC_PARTITION; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_PARTITION_VALUE; -import static io.trino.plugin.hive.HivePartitionKey.HIVE_DEFAULT_DYNAMIC_PARTITION; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; 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 d189e96fa4e8..88f7ee2411aa 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 @@ -17,7 +17,7 @@ import io.trino.operator.RetryPolicy; import io.trino.plugin.exchange.filesystem.FileSystemExchangePlugin; import io.trino.plugin.exchange.filesystem.containers.MinioStorage; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.spi.ErrorType; import io.trino.testing.BaseFailureRecoveryTest; import io.trino.testing.QueryRunner; @@ -59,7 +59,7 @@ protected QueryRunner createQueryRunner( Module failureInjectionModule) throws Exception { - HiveMinioDataLake hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + Hive3MinioDataLake hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); MinioStorage minioStorage = closeAfterClass(new MinioStorage("test-exchange-spooling-" + randomNameSuffix())); minioStorage.start(); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeAwsConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeAwsConnectorSmokeTest.java index e7b1529a8f1d..8fd2de505c58 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeAwsConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeAwsConnectorSmokeTest.java @@ -13,8 +13,8 @@ */ package io.trino.plugin.deltalake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.containers.HiveHadoop; -import io.trino.plugin.hive.containers.HiveMinioDataLake; import io.trino.testing.QueryRunner; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.TestInstance; @@ -29,12 +29,12 @@ public abstract class BaseDeltaLakeAwsConnectorSmokeTest extends BaseDeltaLakeConnectorSmokeTest { - protected HiveMinioDataLake hiveMinioDataLake; + protected Hive3MinioDataLake hiveMinioDataLake; @Override protected HiveHadoop createHiveHadoop() { - hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); return hiveMinioDataLake.getHiveHadoop(); // closed by superclass } 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 9c9cde68f35a..4c352a590b57 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 @@ -13,7 +13,7 @@ */ package io.trino.plugin.deltalake; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; @@ -32,7 +32,7 @@ public abstract class BaseDeltaLakeCompatibility { protected final String bucketName; protected final String resourcePath; - protected HiveMinioDataLake hiveMinioDataLake; + protected Hive3MinioDataLake hiveMinioDataLake; public BaseDeltaLakeCompatibility(String resourcePath) { @@ -44,7 +44,7 @@ public BaseDeltaLakeCompatibility(String resourcePath) protected QueryRunner createQueryRunner() throws Exception { - hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); QueryRunner queryRunner = DeltaLakeQueryRunner.builder() 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 b143ce622d3a..2bfd28edd699 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 @@ -2078,7 +2078,7 @@ public void testOptimizeUsingForcedPartitioning() public void testHistoryTable() { String tableName = "test_history_table_" + randomNameSuffix(); - try (TestTable table = new TestTable(getQueryRunner()::execute, tableName, "(int_col INTEGER)")) { + try (TestTable table = newTrinoTable(tableName, "(int_col INTEGER)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 1, 2, 3", 3); assertUpdate("INSERT INTO " + table.getName() + " VALUES 4, 5, 6", 3); assertUpdate("DELETE FROM " + table.getName() + " WHERE int_col = 1", 1); @@ -2097,8 +2097,7 @@ public void testHistoryTable() @Test public void testHistoryTableWithDeletedTransactionLog() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_history_table_with_deleted_transaction_log", "(int_col INTEGER) WITH (checkpoint_interval = 3)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 1, 2, 3", 3); @@ -2323,8 +2322,7 @@ public void testPartitionFilterIncluded() .setCatalogSessionProperty(getSession().getCatalog().orElseThrow(), "query_partition_filter_required", "true") .build(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_no_partition_filter", "(x varchar, part varchar) WITH (PARTITIONED_BY = ARRAY['part'])", ImmutableList.of("'a', 'part_a'", "'b', 'part_b'"))) { @@ -2336,7 +2334,7 @@ public void testPartitionFilterIncluded() @Test public void testCreateOrReplaceTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_table", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { + try (TestTable table = newTrinoTable("test_table", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { assertThat(query("SELECT CAST(a AS bigint), b FROM " + table.getName())) .matches("VALUES (BIGINT '42', -385e-1)"); @@ -2351,7 +2349,7 @@ public void testCreateOrReplaceTable() @Test public void testCreateOrReplaceTableAs() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_table", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { + try (TestTable table = newTrinoTable("test_table", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { assertThat(query("SELECT CAST(a AS bigint), b FROM " + table.getName())) .matches("VALUES (BIGINT '42', -385e-1)"); @@ -2367,7 +2365,7 @@ public void testCreateOrReplaceTableAs() @Test public void testCreateOrReplaceTableChangeColumnNamesAndTypes() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_table", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { + try (TestTable table = newTrinoTable("test_table", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { assertThat(query("SELECT CAST(a AS bigint), b FROM " + table.getName())) .matches("VALUES (BIGINT '42', -385e-1)"); @@ -2391,7 +2389,7 @@ public void testCreateOrReplaceTableConcurrently() CyclicBarrier barrier = new CyclicBarrier(threads + 1); ExecutorService executor = newFixedThreadPool(threads + 1); List> futures = new ArrayList<>(); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace", "(col integer)")) { + try (TestTable table = newTrinoTable("test_create_or_replace", "(col integer)")) { String tableName = table.getName(); getQueryRunner().execute("CREATE OR REPLACE TABLE " + tableName + " AS SELECT 1 a"); 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 471b6c212d62..843d708028e1 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 @@ -17,7 +17,7 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.spi.security.ConnectorIdentity; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; 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 ddbdfbac1148..46fa3e58002a 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 @@ -16,8 +16,8 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.Table; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.MaterializedRow; import org.junit.jupiter.api.Test; 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 01df1213f3d5..5c2ecb1d36f5 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 @@ -19,8 +19,8 @@ import io.airlift.log.Level; import io.airlift.log.Logger; import io.airlift.log.Logging; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.containers.HiveHadoop; -import io.trino.plugin.hive.containers.HiveMinioDataLake; import io.trino.plugin.tpch.TpchPlugin; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; @@ -259,7 +259,7 @@ public static void main(String[] args) { String bucketName = "test-bucket"; - HiveMinioDataLake hiveMinioDataLake = new HiveMinioDataLake(bucketName); + Hive3MinioDataLake hiveMinioDataLake = new Hive3MinioDataLake(bucketName); hiveMinioDataLake.start(); QueryRunner queryRunner = builder() diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/SparkDeltaLake.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/SparkDeltaLake.java index 6ac5e396d839..9742c853c5ef 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/SparkDeltaLake.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/SparkDeltaLake.java @@ -14,8 +14,8 @@ package io.trino.plugin.deltalake; import io.trino.plugin.base.util.AutoCloseableCloser; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.containers.HiveHadoop; -import io.trino.plugin.hive.containers.HiveMinioDataLake; import io.trino.testing.containers.Minio; import org.testcontainers.containers.GenericContainer; @@ -26,11 +26,11 @@ public final class SparkDeltaLake implements AutoCloseable { private final AutoCloseableCloser closer = AutoCloseableCloser.create(); - private final HiveMinioDataLake hiveMinio; + private final Hive3MinioDataLake hiveMinio; public SparkDeltaLake(String bucketName) { - hiveMinio = closer.register(new HiveMinioDataLake(bucketName)); + hiveMinio = closer.register(new Hive3MinioDataLake(bucketName)); hiveMinio.start(); closer.register(new GenericContainer<>("ghcr.io/trinodb/testing/spark3-delta:" + getDockerImagesVersion())) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAlluxioCacheFileOperations.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAlluxioCacheFileOperations.java index 32bdc7c2cb7c..dc9f72d210e6 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAlluxioCacheFileOperations.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAlluxioCacheFileOperations.java @@ -95,12 +95,12 @@ public void testCacheFileOperations() .add(new CacheOperation("Alluxio.writeCache", "00000000000000000002.json", 0, 658)) .add(new CacheOperation("InputFile.length", "00000000000000000003.json")) .add(new CacheOperation("InputFile.newStream", "_last_checkpoint")) - .add(new CacheOperation("Alluxio.readCached", "key=p1/", 0, 220)) - .add(new CacheOperation("Alluxio.readCached", "key=p2/", 0, 220)) - .add(new CacheOperation("Input.readFully", "key=p1/", 0, 220)) - .add(new CacheOperation("Input.readFully", "key=p2/", 0, 220)) - .add(new CacheOperation("Alluxio.writeCache", "key=p1/", 0, 220)) - .add(new CacheOperation("Alluxio.writeCache", "key=p2/", 0, 220)) + .add(new CacheOperation("Alluxio.readCached", "key=p1/", 0, 229)) + .add(new CacheOperation("Alluxio.readCached", "key=p2/", 0, 229)) + .add(new CacheOperation("Input.readFully", "key=p1/", 0, 229)) + .add(new CacheOperation("Input.readFully", "key=p2/", 0, 229)) + .add(new CacheOperation("Alluxio.writeCache", "key=p1/", 0, 229)) + .add(new CacheOperation("Alluxio.writeCache", "key=p2/", 0, 229)) .build()); assertFileSystemAccesses( "SELECT * FROM test_cache_file_operations", @@ -113,8 +113,8 @@ public void testCacheFileOperations() .add(new CacheOperation("InputFile.length", "00000000000000000002.json")) .add(new CacheOperation("InputFile.length", "00000000000000000003.json")) .add(new CacheOperation("InputFile.newStream", "_last_checkpoint")) - .add(new CacheOperation("Alluxio.readCached", "key=p1/", 0, 220)) - .add(new CacheOperation("Alluxio.readCached", "key=p2/", 0, 220)) + .add(new CacheOperation("Alluxio.readCached", "key=p1/", 0, 229)) + .add(new CacheOperation("Alluxio.readCached", "key=p2/", 0, 229)) .build()); assertUpdate("INSERT INTO test_cache_file_operations VALUES ('p3', '3-xyz')", 1); assertUpdate("INSERT INTO test_cache_file_operations VALUES ('p4', '4-xyz')", 1); @@ -139,17 +139,17 @@ public void testCacheFileOperations() .add(new CacheOperation("InputFile.length", "00000000000000000005.json")) .add(new CacheOperation("InputFile.length", "00000000000000000006.json")) .add(new CacheOperation("InputFile.newStream", "_last_checkpoint")) - .add(new CacheOperation("Alluxio.readCached", "key=p1/", 0, 220)) - .add(new CacheOperation("Alluxio.readCached", "key=p2/", 0, 220)) - .add(new CacheOperation("Alluxio.readCached", "key=p3/", 0, 220)) - .add(new CacheOperation("Alluxio.readCached", "key=p4/", 0, 220)) - .add(new CacheOperation("Alluxio.readCached", "key=p5/", 0, 220)) - .add(new CacheOperation("Input.readFully", "key=p3/", 0, 220)) - .add(new CacheOperation("Input.readFully", "key=p4/", 0, 220)) - .add(new CacheOperation("Input.readFully", "key=p5/", 0, 220)) - .add(new CacheOperation("Alluxio.writeCache", "key=p3/", 0, 220)) - .add(new CacheOperation("Alluxio.writeCache", "key=p4/", 0, 220)) - .add(new CacheOperation("Alluxio.writeCache", "key=p5/", 0, 220)) + .add(new CacheOperation("Alluxio.readCached", "key=p1/", 0, 229)) + .add(new CacheOperation("Alluxio.readCached", "key=p2/", 0, 229)) + .add(new CacheOperation("Alluxio.readCached", "key=p3/", 0, 229)) + .add(new CacheOperation("Alluxio.readCached", "key=p4/", 0, 229)) + .add(new CacheOperation("Alluxio.readCached", "key=p5/", 0, 229)) + .add(new CacheOperation("Input.readFully", "key=p3/", 0, 229)) + .add(new CacheOperation("Input.readFully", "key=p4/", 0, 229)) + .add(new CacheOperation("Input.readFully", "key=p5/", 0, 229)) + .add(new CacheOperation("Alluxio.writeCache", "key=p3/", 0, 229)) + .add(new CacheOperation("Alluxio.writeCache", "key=p4/", 0, 229)) + .add(new CacheOperation("Alluxio.writeCache", "key=p5/", 0, 229)) .build()); assertFileSystemAccesses( "SELECT * FROM test_cache_file_operations", @@ -168,11 +168,11 @@ public void testCacheFileOperations() .add(new CacheOperation("InputFile.length", "00000000000000000005.json")) .add(new CacheOperation("InputFile.length", "00000000000000000006.json")) .add(new CacheOperation("InputFile.newStream", "_last_checkpoint")) - .addCopies(new CacheOperation("Alluxio.readCached", "key=p1/", 0, 220), 1) - .addCopies(new CacheOperation("Alluxio.readCached", "key=p2/", 0, 220), 1) - .addCopies(new CacheOperation("Alluxio.readCached", "key=p3/", 0, 220), 1) - .addCopies(new CacheOperation("Alluxio.readCached", "key=p4/", 0, 220), 1) - .addCopies(new CacheOperation("Alluxio.readCached", "key=p5/", 0, 220), 1) + .addCopies(new CacheOperation("Alluxio.readCached", "key=p1/", 0, 229), 1) + .addCopies(new CacheOperation("Alluxio.readCached", "key=p2/", 0, 229), 1) + .addCopies(new CacheOperation("Alluxio.readCached", "key=p3/", 0, 229), 1) + .addCopies(new CacheOperation("Alluxio.readCached", "key=p4/", 0, 229), 1) + .addCopies(new CacheOperation("Alluxio.readCached", "key=p5/", 0, 229), 1) .build()); } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAlluxioCacheMutableTransactionLog.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAlluxioCacheMutableTransactionLog.java index 697543903322..a69d2df8a19d 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAlluxioCacheMutableTransactionLog.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAlluxioCacheMutableTransactionLog.java @@ -77,24 +77,24 @@ public void testTableDataCachedWhileTransactionLogNotCached() ImmutableMultiset.builder() .addCopies(new CacheFileSystemTraceUtils.CacheOperation("InputFile.length", "00000000000000000002.checkpoint.parquet"), 2) .addCopies(new CacheFileSystemTraceUtils.CacheOperation("Input.readTail", "00000000000000000002.checkpoint.parquet"), 2) - .add(new CacheFileSystemTraceUtils.CacheOperation("InputFile.newStream", "00000000000000000003.json")) + .add(new CacheFileSystemTraceUtils.CacheOperation("InputFile.length", "00000000000000000003.json")) .add(new CacheFileSystemTraceUtils.CacheOperation("InputFile.newStream", "_last_checkpoint")) - .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.readCached", "key=p1/", 0, 220)) - .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.readCached", "key=p2/", 0, 220)) - .add(new CacheFileSystemTraceUtils.CacheOperation("Input.readFully", "key=p1/", 0, 220)) - .add(new CacheFileSystemTraceUtils.CacheOperation("Input.readFully", "key=p2/", 0, 220)) - .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.writeCache", "key=p1/", 0, 220)) - .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.writeCache", "key=p2/", 0, 220)) + .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.readCached", "key=p1/", 0, 229)) + .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.readCached", "key=p2/", 0, 229)) + .add(new CacheFileSystemTraceUtils.CacheOperation("Input.readFully", "key=p1/", 0, 229)) + .add(new CacheFileSystemTraceUtils.CacheOperation("Input.readFully", "key=p2/", 0, 229)) + .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.writeCache", "key=p1/", 0, 229)) + .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.writeCache", "key=p2/", 0, 229)) .build()); assertFileSystemAccesses( "SELECT * FROM test_transaction_log_not_cached", ImmutableMultiset.builder() .addCopies(new CacheFileSystemTraceUtils.CacheOperation("InputFile.length", "00000000000000000002.checkpoint.parquet"), 2) .addCopies(new CacheFileSystemTraceUtils.CacheOperation("Input.readTail", "00000000000000000002.checkpoint.parquet"), 2) - .add(new CacheFileSystemTraceUtils.CacheOperation("InputFile.newStream", "00000000000000000003.json")) + .add(new CacheFileSystemTraceUtils.CacheOperation("InputFile.length", "00000000000000000003.json")) .add(new CacheFileSystemTraceUtils.CacheOperation("InputFile.newStream", "_last_checkpoint")) - .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.readCached", "key=p1/", 0, 220)) - .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.readCached", "key=p2/", 0, 220)) + .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.readCached", "key=p1/", 0, 229)) + .add(new CacheFileSystemTraceUtils.CacheOperation("Alluxio.readCached", "key=p2/", 0, 229)) .build()); } 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 bf5ccba08a1d..b17d8344812b 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 @@ -42,6 +42,7 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.airlift.testing.Closeables.closeAllSuppress; +import static io.trino.plugin.deltalake.DeltaLakeConfig.DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.EXTENDED_STATISTICS_COLLECT_ON_WRITE; import static io.trino.plugin.deltalake.DeltaTestingConnectorSession.SESSION; import static io.trino.plugin.deltalake.TestingDeltaLakeUtils.copyDirectoryContents; @@ -450,8 +451,7 @@ public void testAnalyzeSomeColumns() @Test public void testDropExtendedStats() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_drop_extended_stats", "AS SELECT * FROM tpch.sf1.nation")) { String query = "SHOW STATS FOR " + table.getName(); @@ -484,8 +484,7 @@ public void testDropExtendedStats() @Test public void testDropMissingStats() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_drop_missing_stats", "AS SELECT * FROM tpch.sf1.nation")) { // When there are no extended stats, the procedure should have no effect @@ -505,8 +504,7 @@ public void testDropMissingStats() @Test public void testDropStatsAccessControl() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_deny_drop_stats", "AS SELECT * FROM tpch.sf1.nation")) { assertAccessDenied( @@ -523,8 +521,7 @@ public void testDropStatsAccessControl() @Test public void testStatsOnTpcDsData() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_old_date_stats", "AS SELECT d_date FROM tpcds.tiny.date_dim")) { assertUpdate("ANALYZE " + table.getName()); @@ -1009,7 +1006,9 @@ public void testNoColumnStatsMixedCase() """); // Version 3 should be created with recalculated statistics. - List transactionLogAfterUpdate = getEntriesFromJson(3, tableLocation + "/_delta_log", FILE_SYSTEM).orElseThrow(); + List transactionLogAfterUpdate = getEntriesFromJson(3, tableLocation + "/_delta_log", FILE_SYSTEM, DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE) + .orElseThrow() + .getEntriesList(FILE_SYSTEM); assertThat(transactionLogAfterUpdate).hasSize(2); AddFileEntry updateAddFileEntry = transactionLogAfterUpdate.get(1).getAdd(); DeltaLakeFileStatistics updateStats = updateAddFileEntry.getStats().orElseThrow(); 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 8d1db3984a13..617514e09f41 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 @@ -39,6 +39,7 @@ import io.trino.plugin.deltalake.transactionlog.MetadataEntry; import io.trino.plugin.deltalake.transactionlog.ProtocolEntry; import io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointSchemaManager; +import io.trino.plugin.deltalake.transactionlog.checkpoint.TransactionLogTail; import io.trino.plugin.deltalake.transactionlog.statistics.DeltaLakeFileStatistics; import io.trino.plugin.hive.parquet.TrinoParquetDataSource; import io.trino.spi.Page; @@ -85,11 +86,11 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.trino.parquet.ParquetTestUtils.createParquetReader; +import static io.trino.plugin.deltalake.DeltaLakeConfig.DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE; import static io.trino.plugin.deltalake.DeltaTestingConnectorSession.SESSION; import static io.trino.plugin.deltalake.TestingDeltaLakeUtils.copyDirectoryContents; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.extractPartitionColumns; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.getColumnsMetadata; -import static io.trino.plugin.deltalake.transactionlog.checkpoint.TransactionLogTail.getEntriesFromJson; 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.type.DateTimeEncoding.packDateTimeWithZone; @@ -123,6 +124,7 @@ public class TestDeltaLakeBasic new ResourceTable("stats_with_minmax_nulls", "deltalake/stats_with_minmax_nulls"), new ResourceTable("no_column_stats", "databricks73/no_column_stats"), new ResourceTable("liquid_clustering", "deltalake/liquid_clustering"), + new ResourceTable("region_91_lts", "databricks91/region"), new ResourceTable("timestamp_ntz", "databricks131/timestamp_ntz"), new ResourceTable("timestamp_ntz_partition", "databricks131/timestamp_ntz_partition"), new ResourceTable("uniform_hudi", "deltalake/uniform_hudi"), @@ -157,6 +159,7 @@ protected QueryRunner createQueryRunner() .addDeltaProperty("hive.metastore.catalog.dir", catalogDir.toUri().toString()) .addDeltaProperty("delta.register-table-procedure.enabled", "true") .addDeltaProperty("delta.enable-non-concurrent-writes", "true") + .addDeltaProperty("delta.transaction-log.max-cached-file-size", "0B") // Tests json log streaming code path .build(); } @@ -206,6 +209,14 @@ public void testSimpleQueries() } } + @Test + void testDatabricks91() + { + assertThat(query("SELECT * FROM region_91_lts")) + .skippingTypesCheck() // name and comment columns are unbounded varchar in Delta Lake and bounded varchar in TPCH + .matches("SELECT * FROM tpch.tiny.region"); + } + @Test public void testNoColumnStats() { @@ -336,8 +347,7 @@ private void testPartitionValuesParsedCheckpoint(ColumnMappingMode columnMapping checkArgument(inputValues.size() == 2, "inputValues size must be 2"); checkArgument(expectedPartitionValuesParsed.size() == 2, "expectedPartitionValuesParsed size must be 2"); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_values_parsed_checkpoint", "(x int, part " + inputType + ") WITH (checkpoint_interval = 2, column_mapping_mode = '" + columnMappingMode + "', partitioned_by = ARRAY['part'])")) { for (String inputValue : inputValues) { @@ -431,7 +441,7 @@ private void testOptimizeWithColumnMappingMode(String columnMappingMode) "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); // Verify 'add' entry contains the expected physical name in the stats - List transactionLog = getEntriesFromJson(4, tableLocation.resolve("_delta_log").toString(), FILE_SYSTEM).orElseThrow(); + List transactionLog = getEntriesFromJson(4, tableLocation.resolve("_delta_log").toString()); assertThat(transactionLog).hasSize(5); assertThat(transactionLog.get(0).getCommitInfo()).isNotNull(); assertThat(transactionLog.get(1).getRemove()).isNotNull(); @@ -695,7 +705,7 @@ public void testStatisticsWithColumnCaseSensitivity() assertUpdate("INSERT INTO " + tableName + " VALUES (10, 1), (20, 1), (null, 1)", 3); - List transactionLog = getEntriesFromJson(1, tableLocation.resolve("_delta_log").toString(), FILE_SYSTEM).orElseThrow(); + List transactionLog = getEntriesFromJson(1, tableLocation.resolve("_delta_log").toString()); assertThat(transactionLog).hasSize(2); AddFileEntry addFileEntry = transactionLog.get(1).getAdd(); DeltaLakeFileStatistics stats = addFileEntry.getStats().orElseThrow(); @@ -705,7 +715,7 @@ public void testStatisticsWithColumnCaseSensitivity() assertUpdate("UPDATE " + tableName + " SET upper_case = upper_case + 10", 3); - List transactionLogAfterUpdate = getEntriesFromJson(2, tableLocation.resolve("_delta_log").toString(), FILE_SYSTEM).orElseThrow(); + List transactionLogAfterUpdate = getEntriesFromJson(2, tableLocation.resolve("_delta_log").toString()); assertThat(transactionLogAfterUpdate).hasSize(3); AddFileEntry updateAddFileEntry = transactionLogAfterUpdate.get(2).getAdd(); DeltaLakeFileStatistics updateStats = updateAddFileEntry.getStats().orElseThrow(); @@ -859,7 +869,7 @@ private void testTrinoCreateTableWithTimestampNtz(ZoneId sessionZone, Consumer transactionLogs = getEntriesFromJson(0, tableLocation + "/_delta_log", FILE_SYSTEM).orElseThrow(); + List transactionLogs = getEntriesFromJson(0, tableLocation + "/_delta_log"); ProtocolEntry protocolEntry = transactionLogs.get(1).getProtocol(); assertThat(protocolEntry).isNotNull(); assertThat(protocolEntry.minReaderVersion()).isEqualTo(3); @@ -1035,7 +1045,7 @@ private void testTimestampNtzPartitioned(ZoneId sessionZone) ('part', null, 8.0, 0.1111111111111111, null, null, null), (null, null, null, null, 9.0, null, null) """); - List transactionLogs = getEntriesFromJson(2, tableLocation.resolve("_delta_log").toString(), FILE_SYSTEM).orElseThrow(); + List transactionLogs = getEntriesFromJson(2, tableLocation.resolve("_delta_log").toString()); assertThat(transactionLogs).hasSize(2); AddFileEntry addFileEntry = transactionLogs.get(1).getAdd(); assertThat(addFileEntry).isNotNull(); @@ -1057,7 +1067,7 @@ public void testAddTimestampNtzColumn() assertQuery("SELECT * FROM " + tableName, "VALUES (1, TIMESTAMP '2023-01-02 03:04:05.123456')"); String tableLocation = getTableLocation(tableName); - List transactionLogsByCreateTable = getEntriesFromJson(0, tableLocation + "/_delta_log", FILE_SYSTEM).orElseThrow(); + List transactionLogsByCreateTable = getEntriesFromJson(0, tableLocation + "/_delta_log"); ProtocolEntry protocolEntryByCreateTable = transactionLogsByCreateTable.get(1).getProtocol(); assertThat(protocolEntryByCreateTable).isNotNull(); assertThat(protocolEntryByCreateTable.minReaderVersion()).isEqualTo(1); @@ -1065,7 +1075,7 @@ public void testAddTimestampNtzColumn() assertThat(protocolEntryByCreateTable.readerFeatures()).isEmpty(); assertThat(protocolEntryByCreateTable.writerFeatures()).isEmpty(); - List transactionLogsByAddColumn = getEntriesFromJson(1, tableLocation + "/_delta_log", FILE_SYSTEM).orElseThrow(); + List transactionLogsByAddColumn = getEntriesFromJson(1, tableLocation + "/_delta_log"); ProtocolEntry protocolEntryByAddColumn = transactionLogsByAddColumn.get(1).getProtocol(); assertThat(protocolEntryByAddColumn).isNotNull(); assertThat(protocolEntryByAddColumn.minReaderVersion()).isEqualTo(3); @@ -1090,7 +1100,7 @@ public void testIdentityColumns() assertUpdate("CALL system.register_table(CURRENT_SCHEMA, '%s', '%s')".formatted(tableName, tableLocation.toUri())); assertQueryReturnsEmptyResult("SELECT * FROM " + tableName); - List transactionLog = getEntriesFromJson(0, tableLocation.resolve("_delta_log").toString(), FILE_SYSTEM).orElseThrow(); + List transactionLog = getEntriesFromJson(0, tableLocation.resolve("_delta_log").toString()); assertThat(transactionLog).hasSize(3); MetadataEntry metadataEntry = transactionLog.get(2).getMetaData(); assertThat(getColumnsMetadata(metadataEntry).get("b")) @@ -1102,7 +1112,7 @@ public void testIdentityColumns() // Verify a column operation preserves delta.identity.* column properties assertUpdate("COMMENT ON COLUMN " + tableName + ".b IS 'test column comment'"); - List transactionLogAfterComment = getEntriesFromJson(1, tableLocation.resolve("_delta_log").toString(), FILE_SYSTEM).orElseThrow(); + List transactionLogAfterComment = getEntriesFromJson(1, tableLocation.resolve("_delta_log").toString()); assertThat(transactionLogAfterComment).hasSize(3); MetadataEntry commentMetadataEntry = transactionLogAfterComment.get(2).getMetaData(); assertThat(getColumnsMetadata(commentMetadataEntry).get("b")) @@ -1188,12 +1198,12 @@ public void testDeletionVectorsEnabledCreateTable() private void testDeletionVectorsEnabledCreateTable(String tableDefinition) throws Exception { - try (TestTable table = new TestTable(getQueryRunner()::execute, "deletion_vectors", tableDefinition)) { + try (TestTable table = newTrinoTable("deletion_vectors", tableDefinition)) { assertThat((String) computeScalar("SHOW CREATE TABLE " + table.getName())) .contains("deletion_vectors_enabled = true"); String tableLocation = getTableLocation(table.getName()); - List transactionLogs = getEntriesFromJson(0, tableLocation + "/_delta_log", FILE_SYSTEM).orElseThrow(); + List transactionLogs = getEntriesFromJson(0, tableLocation + "/_delta_log"); assertThat(transactionLogs.get(1).getProtocol()) .isEqualTo(new ProtocolEntry(3, 7, Optional.of(Set.of("deletionVectors")), Optional.of(Set.of("deletionVectors")))); @@ -1221,12 +1231,12 @@ public void testDeletionVectorsDisabledCreateTable() private void testDeletionVectorsDisabledCreateTable(String tableDefinition) throws Exception { - try (TestTable table = new TestTable(getQueryRunner()::execute, "deletion_vectors", tableDefinition)) { + try (TestTable table = newTrinoTable("deletion_vectors", tableDefinition)) { assertThat((String) computeScalar("SHOW CREATE TABLE " + table.getName())) .doesNotContain("deletion_vectors_enabled"); String tableLocation = getTableLocation(table.getName()); - List transactionLogs = getEntriesFromJson(0, tableLocation + "/_delta_log", FILE_SYSTEM).orElseThrow(); + List transactionLogs = getEntriesFromJson(0, tableLocation + "/_delta_log"); assertThat(transactionLogs.get(1).getProtocol()) .isEqualTo(new ProtocolEntry(1, 2, Optional.empty(), Optional.empty())); @@ -1295,8 +1305,8 @@ public void testDeletionVectorsAllRows() assertUpdate("DELETE FROM " + tableName + " WHERE a != 999", 1); // 'remove' entry should have the same deletion vector as the previous operation when deleting all rows - DeletionVectorEntry deletionVector = getEntriesFromJson(2, tableLocation + "/_delta_log", FILE_SYSTEM).orElseThrow().get(2).getAdd().getDeletionVector().orElseThrow(); - assertThat(getEntriesFromJson(3, tableLocation + "/_delta_log", FILE_SYSTEM).orElseThrow().get(1).getRemove().deletionVector().orElseThrow()) + DeletionVectorEntry deletionVector = getEntriesFromJson(2, tableLocation + "/_delta_log").get(2).getAdd().getDeletionVector().orElseThrow(); + assertThat(getEntriesFromJson(3, tableLocation + "/_delta_log").get(1).getRemove().deletionVector().orElseThrow()) .isEqualTo(deletionVector); assertUpdate("INSERT INTO " + tableName + " VALUES (3, 31), (3, 32)", 2); @@ -2248,8 +2258,7 @@ public void testUnsupportedWriterVersion() private static MetadataEntry loadMetadataEntry(long entryNumber, Path tableLocation) throws IOException { - TrinoFileSystem fileSystem = new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS).create(SESSION); - DeltaLakeTransactionLogEntry transactionLog = getEntriesFromJson(entryNumber, tableLocation.resolve("_delta_log").toString(), fileSystem).orElseThrow().stream() + DeltaLakeTransactionLogEntry transactionLog = getEntriesFromJson(entryNumber, tableLocation.resolve("_delta_log").toString()).stream() .filter(log -> log.getMetaData() != null) .collect(onlyElement()); return transactionLog.getMetaData(); @@ -2258,8 +2267,7 @@ private static MetadataEntry loadMetadataEntry(long entryNumber, Path tableLocat private static ProtocolEntry loadProtocolEntry(long entryNumber, Path tableLocation) throws IOException { - TrinoFileSystem fileSystem = new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS).create(SESSION); - DeltaLakeTransactionLogEntry transactionLog = getEntriesFromJson(entryNumber, tableLocation.resolve("_delta_log").toString(), fileSystem).orElseThrow().stream() + DeltaLakeTransactionLogEntry transactionLog = getEntriesFromJson(entryNumber, tableLocation.resolve("_delta_log").toString()).stream() .filter(log -> log.getProtocol() != null) .collect(onlyElement()); return transactionLog.getProtocol(); @@ -2276,4 +2284,12 @@ private String getTableLocation(String tableName) } throw new IllegalStateException("Location not found in SHOW CREATE TABLE result"); } + + private static List getEntriesFromJson(long entryNumber, String transactionLogDir) + throws IOException + { + return TransactionLogTail.getEntriesFromJson(entryNumber, transactionLogDir, FILE_SYSTEM, DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE) + .orElseThrow() + .getEntriesList(FILE_SYSTEM); + } } 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 6746c8f2a522..9d2447df488d 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 @@ -37,6 +37,7 @@ 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.deltalake.DeltaLakeConfig.DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE; import static io.trino.plugin.deltalake.DeltaTestingConnectorSession.SESSION; import static io.trino.plugin.deltalake.transactionlog.checkpoint.TransactionLogTail.getEntriesFromJson; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; @@ -144,7 +145,9 @@ private static MetadataEntry loadMetadataEntry(long entryNumber, Path tableLocat throws IOException { TrinoFileSystem fileSystem = new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS).create(SESSION); - DeltaLakeTransactionLogEntry transactionLog = getEntriesFromJson(entryNumber, tableLocation.resolve("_delta_log").toString(), fileSystem).orElseThrow().stream() + DeltaLakeTransactionLogEntry transactionLog = getEntriesFromJson(entryNumber, tableLocation.resolve("_delta_log").toString(), fileSystem, DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE) + .orElseThrow() + .getEntriesList(fileSystem).stream() .filter(log -> log.getMetaData() != null) .collect(onlyElement()); return transactionLog.getMetaData(); 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 3ef6e8c0a500..fb4555c6fb1d 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 @@ -44,6 +44,7 @@ public void testDefaults() .setDataFileCacheTtl(new Duration(30, MINUTES)) .setMetadataCacheTtl(new Duration(30, TimeUnit.MINUTES)) .setMetadataCacheMaxRetainedSize(DeltaLakeConfig.DEFAULT_METADATA_CACHE_MAX_RETAINED_SIZE) + .setTransactionLogMaxCachedFileSize(DeltaLakeConfig.DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE) .setDomainCompactionThreshold(1000) .setMaxSplitsPerSecond(Integer.MAX_VALUE) .setMaxOutstandingSplits(1_000) @@ -60,7 +61,7 @@ public void testDefaults() .setTableStatisticsEnabled(true) .setExtendedStatisticsEnabled(true) .setCollectExtendedStatisticsOnWrite(true) - .setCompressionCodec(HiveCompressionCodec.SNAPPY) + .setCompressionCodec(HiveCompressionCodec.ZSTD) .setDeleteSchemaLocationsFallback(false) .setParquetTimeZone(TimeZone.getDefault().getID()) .setPerTransactionMetastoreCacheMaximumSize(1000) @@ -84,6 +85,7 @@ public void testExplicitPropertyMappings() Map properties = ImmutableMap.builder() .put("delta.metadata.cache-ttl", "10m") .put("delta.metadata.cache-max-retained-size", "1GB") + .put("delta.transaction-log.max-cached-file-size", "1MB") .put("delta.metadata.live-files.cache-size", "0 MB") .put("delta.metadata.live-files.cache-ttl", "60m") .put("delta.domain-compaction-threshold", "500") @@ -125,6 +127,7 @@ public void testExplicitPropertyMappings() .setDataFileCacheTtl(new Duration(60, MINUTES)) .setMetadataCacheTtl(new Duration(10, TimeUnit.MINUTES)) .setMetadataCacheMaxRetainedSize(DataSize.of(1, GIGABYTE)) + .setTransactionLogMaxCachedFileSize(DataSize.of(1, MEGABYTE)) .setDomainCompactionThreshold(500) .setMaxOutstandingSplits(200) .setMaxSplitsPerSecond(10) 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 943caec8b254..c16c2c1c641c 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 @@ -24,10 +24,10 @@ import io.trino.Session; import io.trino.execution.QueryInfo; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.Table; import io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.ColumnMappingMode; import io.trino.plugin.hive.HiveCompressionCodec; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.tpch.TpchPlugin; import io.trino.spi.connector.ColumnMetadata; import io.trino.sql.planner.plan.FilterNode; @@ -344,8 +344,7 @@ public void testPartialFilterWhenPartitionColumnOrderIsDifferentFromTableDefinit private void testPartialFilterWhenPartitionColumnOrderIsDifferentFromTableDefinition(ColumnMappingMode columnMappingMode) { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_delete_with_partial_filter_composed_partition", "(_bigint BIGINT, _date DATE, _varchar VARCHAR) WITH (column_mapping_mode='" + columnMappingMode + "', partitioned_by = ARRAY['_varchar', '_date'])")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (1, CAST('2019-09-10' AS DATE), 'a'), (2, CAST('2019-09-10' AS DATE), 'a')", 2); @@ -403,8 +402,7 @@ public void testInsertIntoUnsupportedVarbinaryPartitionType() { // TODO https://github.com/trinodb/trino/issues/24155 Cannot insert varbinary values into partitioned columns // Update TestDeltaLakeBasic.testPartitionValuesParsedCheckpoint() when fixing this issue - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_varbinary_partition", "(x int, part varbinary) WITH (partitioned_by = ARRAY['part'])")) { assertQueryFails("INSERT INTO " + table.getName() + " VALUES (1, X'01')", "Unsupported type for partition: varbinary"); @@ -588,8 +586,7 @@ public void testRenameColumnName() public void testCharVarcharComparison() { // with char->varchar coercion on table creation, this is essentially varchar/varchar comparison - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_char_varchar", "(k, v) AS VALUES" + " (-1, CAST(NULL AS CHAR(3))), " + @@ -818,7 +815,7 @@ public void testTimestampWithTimeZoneOptimization() @Test public void testShowStatsForTimestampWithTimeZone() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_stats_timestamptz_", "(x TIMESTAMP(3) WITH TIME ZONE) WITH (checkpoint_interval = 2)")) { + try (TestTable table = newTrinoTable("test_stats_timestamptz_", "(x TIMESTAMP(3) WITH TIME ZONE) WITH (checkpoint_interval = 2)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (TIMESTAMP '+10000-01-02 13:34:56.123 +01:00')", 1); assertThat(query("SHOW STATS FOR " + table.getName())) .result() @@ -843,7 +840,7 @@ public void testShowStatsForTimestampWithTimeZone() @Test public void testAddColumnToPartitionedTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_column_partitioned_table_", "(x VARCHAR, part VARCHAR) WITH (partitioned_by = ARRAY['part'])")) { + try (TestTable table = newTrinoTable("test_add_column_partitioned_table_", "(x VARCHAR, part VARCHAR) WITH (partitioned_by = ARRAY['part'])")) { assertUpdate("INSERT INTO " + table.getName() + " SELECT 'first', 'part-0001'", 1); assertQueryFails("ALTER TABLE " + table.getName() + " ADD COLUMN x bigint", ".* Column 'x' already exists"); assertQueryFails("ALTER TABLE " + table.getName() + " ADD COLUMN part bigint", ".* Column 'part' already exists"); @@ -877,7 +874,7 @@ private QueryInfo getQueryInfo(QueryRunner queryRunner, MaterializedResultWithPl @Test public void testAddColumnAndOptimize() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_column_and_optimize", "(x VARCHAR)")) { + try (TestTable table = newTrinoTable("test_add_column_and_optimize", "(x VARCHAR)")) { assertUpdate("INSERT INTO " + table.getName() + " SELECT 'first'", 1); assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN a varchar(50)"); @@ -905,7 +902,7 @@ public void testAddColumnAndVacuum() .setCatalogSessionProperty(getSession().getCatalog().orElseThrow(), "vacuum_min_retention", "0s") .build(); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_column_and_optimize", "(x VARCHAR)")) { + try (TestTable table = newTrinoTable("test_add_column_and_optimize", "(x VARCHAR)")) { assertUpdate("INSERT INTO " + table.getName() + " SELECT 'first'", 1); assertUpdate("INSERT INTO " + table.getName() + " SELECT 'second'", 1); @@ -1009,7 +1006,7 @@ public void testTargetMaxFileSize() @Test public void testPathColumn() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_path_column", "(x VARCHAR)")) { + try (TestTable table = newTrinoTable("test_path_column", "(x VARCHAR)")) { assertUpdate("INSERT INTO " + table.getName() + " SELECT 'first'", 1); String firstFilePath = (String) computeScalar("SELECT \"$path\" FROM " + table.getName()); assertUpdate("INSERT INTO " + table.getName() + " SELECT 'second'", 1); @@ -1331,11 +1328,11 @@ private void testTableWithNonNullableColumns(ColumnMappingMode mode) public void testCreateTableWithChangeDataFeedColumnName() { for (String columnName : CHANGE_DATA_FEED_COLUMN_NAMES) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_table_cdf", "(" + columnName + " int)")) { + try (TestTable table = newTrinoTable("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)) { + try (TestTable table = newTrinoTable("test_create_table_cdf", "AS SELECT 1 AS " + columnName)) { assertTableColumnNames(table.getName(), columnName); } } @@ -1344,7 +1341,7 @@ public void testCreateTableWithChangeDataFeedColumnName() @Test public void testCreateTableWithChangeDataFeed() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cdf", "(x int) WITH (change_data_feed_enabled = true)")) { + try (TestTable table = newTrinoTable("test_cdf", "(x int) WITH (change_data_feed_enabled = true)")) { assertThat(query("SELECT * FROM \"" + table.getName() + "$properties\"")) .skippingTypesCheck() .matches("VALUES " + @@ -1355,7 +1352,7 @@ public void testCreateTableWithChangeDataFeed() } // timestamp type requires reader version 3 and writer version 7 - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cdf", "(x timestamp) WITH (change_data_feed_enabled = true)")) { + try (TestTable table = newTrinoTable("test_cdf", "(x timestamp) WITH (change_data_feed_enabled = true)")) { assertThat(query("SELECT * FROM \"" + table.getName() + "$properties\"")) .skippingTypesCheck() .matches("VALUES " + @@ -1371,8 +1368,7 @@ public void testCreateTableWithChangeDataFeed() @Test public void testChangeDataFeedWithDeletionVectors() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_cdf", "(x VARCHAR, y INT) WITH (change_data_feed_enabled = true, deletion_vectors_enabled = true)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES('test1', 1)", 1); @@ -1415,7 +1411,7 @@ public void testUnsupportedCreateTableWithChangeDataFeed() 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)")) { + try (TestTable table = newTrinoTable("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)); @@ -1432,7 +1428,7 @@ public void testUnsupportedAddColumnWithChangeDataFeed() 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)")) { + try (TestTable table = newTrinoTable("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"); @@ -1445,7 +1441,7 @@ public void testUnsupportedRenameColumnWithChangeDataFeed() public void testUnsupportedSetTablePropertyWithChangeDataFeed() { for (String columnName : CHANGE_DATA_FEED_COLUMN_NAMES) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_properties", "(" + columnName + " int)")) { + try (TestTable table = newTrinoTable("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)); @@ -1534,7 +1530,7 @@ private void testCreateTableColumnMappingMode(ColumnMappingMode mode, Consumer catalogProperties = getSession().getCatalogProperties(getSession().getCatalog().orElseThrow()); assertThat(catalogProperties).doesNotContainKey("query_partition_filter_required"); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_filter_not_demanded", "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("'a', 'part_a'", "'b', 'part_b'"))) { @@ -3717,8 +3697,7 @@ public void testPartitionFilterQueryNotDemanded() public void testQueryWithoutPartitionOnNonPartitionedTableNotDemanded() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_no_partition_table_", "(x varchar, part varchar)", ImmutableList.of("('a', 'part_a')", "('b', 'part_b')"))) { @@ -3731,8 +3710,7 @@ public void testQueryWithoutPartitionOnNonPartitionedTableNotDemanded() public void testQueryWithoutPartitionFilterNotAllowed() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_no_partition_filter_", "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("('a', 'part_a')", "('b', 'part_b')"))) { @@ -3747,8 +3725,7 @@ public void testQueryWithoutPartitionFilterNotAllowed() public void testPartitionFilterRemovedByPlanner() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_filter_removed_", "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("('a', 'part_a')", "('b', 'part_b')"))) { @@ -3763,8 +3740,7 @@ public void testPartitionFilterRemovedByPlanner() public void testPartitionFilterIncluded() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_filter_included", "(x varchar, part integer) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("('a', 1)", "('a', 2)", "('a', 3)", "('a', 4)", "('b', 1)", "('b', 2)", "('b', 3)", "('b', 4)"))) { @@ -3792,8 +3768,7 @@ public void testRequiredPartitionFilterOnJoin() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable leftTable = new TestTable( - getQueryRunner()::execute, + try (TestTable leftTable = newTrinoTable( "test_partition_left_", "(x varchar, part varchar)", ImmutableList.of("('a', 'part_a')")); @@ -3818,8 +3793,7 @@ public void testRequiredPartitionFilterOnJoinBothTablePartitioned() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable leftTable = new TestTable( - getQueryRunner()::execute, + try (TestTable leftTable = newTrinoTable( "test_partition_inferred_left_", "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("('a', 'part_a')")); @@ -3843,8 +3817,7 @@ public void testRequiredPartitionFilterOnJoinBothTablePartitioned() public void testComplexPartitionPredicateWithCasting() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_predicate", "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("('a', '1')", "('b', '2')"))) { @@ -3856,8 +3829,7 @@ public void testComplexPartitionPredicateWithCasting() public void testPartitionPredicateInOuterQuery() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_predicate", "(x integer, part integer) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("(1, 11)", "(2, 22)"))) { @@ -3869,8 +3841,7 @@ public void testPartitionPredicateInOuterQuery() public void testPartitionPredicateInInnerQuery() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_predicate", "(x integer, part integer) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("(1, 11)", "(2, 22)"))) { @@ -3882,8 +3853,7 @@ public void testPartitionPredicateInInnerQuery() public void testPartitionPredicateFilterAndAnalyzeOnPartitionedTable() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_predicate_analyze_", "(x integer, part integer) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("(1, 11)", "(2, 22)"))) { @@ -3898,8 +3868,7 @@ public void testPartitionPredicateFilterAndAnalyzeOnPartitionedTable() public void testPartitionPredicateFilterAndAnalyzeOnNonPartitionedTable() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable nonPartitioned = new TestTable( - getQueryRunner()::execute, + try (TestTable nonPartitioned = newTrinoTable( "test_partition_predicate_analyze_nonpartitioned", "(a integer, b integer) ", ImmutableList.of("(1, 11)", "(2, 22)"))) { @@ -3912,8 +3881,7 @@ public void testPartitionPredicateFilterAndAnalyzeOnNonPartitionedTable() public void testPartitionFilterMultiplePartition() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_filter_multiple_partition_", "(x varchar, part1 integer, part2 integer) WITH (partitioned_by = ARRAY['part1', 'part2'])", ImmutableList.of("('a', 1, 1)", "('a', 1, 2)", "('a', 2, 1)", "('a', 2, 2)", "('b', 1, 1)", "('b', 1, 2)", "('b', 2, 1)", "('b', 2, 2)"))) { @@ -3942,8 +3910,7 @@ public void testPartitionFilterMultiplePartition() public void testPartitionFilterRequiredAndOptimize() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_filter_optimize", "(part integer, name varchar(50)) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("(1, 'Bob')", "(2, 'Alice')"))) { @@ -3976,8 +3943,7 @@ public void testPartitionFilterRequiredAndOptimize() public void testPartitionFilterEnabledAndOptimizeForNonPartitionedTable() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_filter_nonpartitioned_optimize", "(part integer, name varchar(50))", ImmutableList.of("(1, 'Bob')", "(2, 'Alice')"))) { @@ -4002,8 +3968,7 @@ public void testPartitionFilterEnabledAndOptimizeForNonPartitionedTable() public void testPartitionFilterRequiredAndWriteOperation() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_filter_table_changes", "(x integer, part integer) WITH (partitioned_by = ARRAY['part'], change_data_feed_enabled = true)", ImmutableList.of("(1, 11)", "(2, 22)", "(3, 33)"))) { @@ -4041,8 +4006,7 @@ public void testPartitionFilterRequiredAndWriteOperation() public void testPartitionFilterRequiredAndTableChanges() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_filter_table_changes", "(x integer, part integer) WITH (partitioned_by = ARRAY['part'], change_data_feed_enabled = true)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (1, 11)", 1); @@ -4093,8 +4057,7 @@ public void testPartitionFilterRequiredAndTableChanges() public void testPartitionFilterRequiredAndHistoryTable() { Session session = sessionWithPartitionFilterRequirement(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partition_filter_table_changes", "(x integer, part integer) WITH (partitioned_by = ARRAY['part'], change_data_feed_enabled = true)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (1, 11)", 1); @@ -4274,8 +4237,7 @@ public void testTypeCoercionOnCreateTable() private void testTimestampCoercionOnCreateTable(@Language("SQL") String actualValue, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_timestamp_coercion_on_create_table", "(ts TIMESTAMP)")) { assertUpdate("INSERT INTO " + testTable.getName() + " VALUES (" + actualValue + ")", 1); @@ -4287,8 +4249,7 @@ private void testTimestampCoercionOnCreateTable(@Language("SQL") String actualVa private void testCharCoercionOnCreateTable(@Language("SQL") String actualValue, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_char_coercion_on_create_table", "(vch VARCHAR)")) { assertUpdate("INSERT INTO " + testTable.getName() + " VALUES (" + actualValue + ")", 1); @@ -4336,8 +4297,7 @@ public void testTypeCoercionOnCreateTableAsSelect() private void testTimestampCoercionOnCreateTableAsSelect(@Language("SQL") String actualValue, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_timestamp_coercion_on_create_table_as_select", "AS SELECT %s ts".formatted(actualValue))) { assertThat(getColumnType(testTable.getName(), "ts")).isEqualTo("timestamp(6)"); @@ -4348,8 +4308,7 @@ private void testTimestampCoercionOnCreateTableAsSelect(@Language("SQL") String private void testCharCoercionOnCreateTableAsSelect(@Language("SQL") String actualValue, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_char_coercion_on_create_table_as_select", "AS SELECT %s col".formatted(actualValue))) { assertThat(getColumnType(testTable.getName(), "col")).isEqualTo("varchar"); @@ -4396,8 +4355,7 @@ public void testTypeCoercionOnCreateTableAsSelectWithNoData() private void testTimestampCoercionOnCreateTableAsSelectWithNoData(@Language("SQL") String actualValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_timestamp_coercion_on_create_table_as_select_with_no_data", "AS SELECT %s ts WITH NO DATA".formatted(actualValue))) { assertThat(getColumnType(testTable.getName(), "ts")).isEqualTo("timestamp(6)"); @@ -4407,8 +4365,7 @@ private void testTimestampCoercionOnCreateTableAsSelectWithNoData(@Language("SQL private void testCharCoercionOnCreateTableAsSelectWithNoData(@Language("SQL") String actualValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_char_coercion_on_create_table_as_select_with_no_data", "AS SELECT %s col WITH NO DATA".formatted(actualValue))) { assertThat(getColumnType(testTable.getName(), "col")).isEqualTo("varchar"); @@ -4455,8 +4412,7 @@ public void testTypeCoercionOnCreateTableAsWithRowType() private void testTimestampCoercionOnCreateTableAsWithRowType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_timestamp_coercion_on_create_table_as_with_row_type", "AS SELECT CAST(row(%s) AS row(value timestamp(6))) ts".formatted(actualValue))) { assertThat(getColumnType(testTable.getName(), "ts")).isEqualTo("row(value timestamp(6))"); @@ -4469,8 +4425,7 @@ private void testTimestampCoercionOnCreateTableAsWithRowType(@Language("SQL") St private void testCharCoercionOnCreateTableAsWithRowType(@Language("SQL") String actualValue, @Language("SQL") String actualTypeLiteral, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_char_coercion_on_create_table_as_with_row_type", "AS SELECT CAST(row(%s) AS row(value %s)) col".formatted(actualValue, actualTypeLiteral))) { assertThat(getColumnType(testTable.getName(), "col")).isEqualTo("row(value varchar)"); @@ -4519,8 +4474,7 @@ public void testTypeCoercionOnCreateTableAsWithArrayType() private void testTimestampCoercionOnCreateTableAsWithArrayType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_timestamp_coercion_on_create_table_as_with_array_type", "AS SELECT array[%s] ts".formatted(actualValue))) { assertThat(getColumnType(testTable.getName(), "ts")).isEqualTo("array(timestamp(6))"); @@ -4533,8 +4487,7 @@ private void testTimestampCoercionOnCreateTableAsWithArrayType(@Language("SQL") private void testCharCoercionOnCreateTableAsWithArrayType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_char_coercion_on_create_table_as_with_array_type", "AS SELECT array[%s] col".formatted(actualValue))) { assertThat(getColumnType(testTable.getName(), "col")).isEqualTo("array(varchar)"); @@ -4583,8 +4536,7 @@ public void testTypeCoercionOnCreateTableAsWithMapType() private void testTimestampCoercionOnCreateTableAsWithMapType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_timestamp_coercion_on_create_table_as_with_map_type", "AS SELECT map(array[%1$s], array[%1$s]) ts".formatted(actualValue))) { assertThat(getColumnType(testTable.getName(), "ts")).isEqualTo("map(timestamp(6), timestamp(6))"); @@ -4597,8 +4549,7 @@ private void testTimestampCoercionOnCreateTableAsWithMapType(@Language("SQL") St private void testCharCoercionOnCreateTableAsWithMapType(@Language("SQL") String actualValue, @Language("SQL") String expectedValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_char_coercion_on_create_table_as_with_map_type", "AS SELECT map(array[%1$s], array[%1$s]) col".formatted(actualValue))) { assertThat(getColumnType(testTable.getName(), "col")).isEqualTo("map(varchar, varchar)"); @@ -4635,7 +4586,7 @@ public void testAddColumnWithTypeCoercion() private void testAddColumnWithTypeCoercion(String columnType, String expectedColumnType) { - try (TestTable testTable = new TestTable(getQueryRunner()::execute, "test_coercion_add_column", "(a varchar, b row(x integer))")) { + try (TestTable testTable = newTrinoTable("test_coercion_add_column", "(a varchar, b row(x integer))")) { // TODO: Update this test once the connector supports adding a new field to a row type assertQueryFails("ALTER TABLE " + testTable.getName() + " ADD COLUMN b.y " + columnType, "This connector does not support adding fields"); @@ -4654,8 +4605,7 @@ private void assertTimestampNtzFeature(String tableName) @Test public void testSelectTableUsingVersion() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_select_table_using_version", "(id INT, country VARCHAR)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (1, 'India')", 1); @@ -4703,7 +4653,7 @@ public void testSelectTableUsingVersion() @Test public void testReadMultipleVersions() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_read_multiple_versions", "AS SELECT 1 id")) { + try (TestTable table = newTrinoTable("test_read_multiple_versions", "AS SELECT 1 id")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 2", 1); assertQuery( "SELECT * FROM " + table.getName() + " FOR VERSION AS OF 0 " + @@ -4716,7 +4666,7 @@ public void testReadMultipleVersions() @Test public void testReadVersionedTableWithOptimize() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_read_versioned_optimize", "AS SELECT 1 id")) { + try (TestTable table = newTrinoTable("test_read_versioned_optimize", "AS SELECT 1 id")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 2", 1); Set beforeActiveFiles = getActiveFiles(table.getName()); @@ -4743,7 +4693,7 @@ public void testReadVersionedTableWithVacuum() .setCatalogSessionProperty(getSession().getCatalog().orElseThrow(), "vacuum_min_retention", "0s") .build(); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_column_and_vacuum", "(x VARCHAR)")) { + try (TestTable table = newTrinoTable("test_add_column_and_vacuum", "(x VARCHAR)")) { assertUpdate("INSERT INTO " + table.getName() + " SELECT 'first'", 1); assertUpdate("INSERT INTO " + table.getName() + " SELECT 'second'", 1); @@ -4784,8 +4734,8 @@ public void testReadVersionedTableWithVacuum() @Test public void testInsertFromVersionedTable() { - try (TestTable targetTable = new TestTable(getQueryRunner()::execute, "test_read_versioned_insert", "(col int)"); - TestTable sourceTable = new TestTable(getQueryRunner()::execute, "test_read_versioned_insert", "AS SELECT 1 col")) { + try (TestTable targetTable = newTrinoTable("test_read_versioned_insert", "(col int)"); + TestTable sourceTable = newTrinoTable("test_read_versioned_insert", "AS SELECT 1 col")) { assertUpdate("INSERT INTO " + sourceTable.getName() + " VALUES 2", 1); assertUpdate("INSERT INTO " + sourceTable.getName() + " VALUES 3", 1); @@ -4800,7 +4750,7 @@ public void testInsertFromVersionedTable() @Test public void testInsertFromVersionedSameTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_read_versioned_insert", "AS SELECT 1 id")) { + try (TestTable table = newTrinoTable("test_read_versioned_insert", "AS SELECT 1 id")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 2", 1); assertUpdate("INSERT INTO " + table.getName() + " SELECT * FROM " + table.getName() + " FOR VERSION AS OF 0", 1); @@ -4824,7 +4774,7 @@ public void testInsertFromVersionedSameTable() @Test public void testInsertFromMultipleVersionedSameTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_read_versioned_insert", "AS SELECT 1 id")) { + try (TestTable table = newTrinoTable("test_read_versioned_insert", "AS SELECT 1 id")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 2", 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES 1, 2"); @@ -4841,7 +4791,7 @@ public void testInsertFromMultipleVersionedSameTable() @Test public void testReadVersionedTableWithChangeDataFeed() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_read_versioned_cdf", "WITH (change_data_feed_enabled=true) AS SELECT 1 id")) { + try (TestTable table = newTrinoTable("test_read_versioned_cdf", "WITH (change_data_feed_enabled=true) AS SELECT 1 id")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 2", 1); assertUpdate("UPDATE " + table.getName() + " SET id = -2 WHERE id = 2", 1); assertUpdate("DELETE FROM " + table.getName() + " WHERE id = 1", 1); @@ -4857,7 +4807,7 @@ public void testReadVersionedTableWithChangeDataFeed() public void testSelectTableUsingVersionSchemaEvolution() { // Delta Lake respects the old schema unlike Iceberg connector - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_select_table_using_version", "AS SELECT 1 id")) { + try (TestTable table = newTrinoTable("test_select_table_using_version", "AS SELECT 1 id")) { assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN new_col VARCHAR"); assertQuery("SELECT * FROM " + table.getName() + " FOR VERSION AS OF 0", "VALUES 1"); assertQuery("SELECT * FROM " + table.getName() + " FOR VERSION AS OF 1", "VALUES (1, NULL)"); @@ -4897,7 +4847,7 @@ public void testSelectTableUsingVersionDeletedCheckpoints() public void testSelectAfterReadVersionedTable() { // Run normal SELECT after reading from versioned table - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_select_after_version", "AS SELECT 1 id")) { + try (TestTable table = newTrinoTable("test_select_after_version", "AS SELECT 1 id")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 2", 1); assertQuery("SELECT * FROM " + table.getName() + " FOR VERSION AS OF 0", "VALUES 1"); assertQuery("SELECT * FROM " + table.getName(), "VALUES 1, 2"); @@ -4965,7 +4915,7 @@ public void testDuplicatedFieldNames() assertQueryFails("CREATE TABLE " + tableName + "(col row(a row(x int, \"X\" int)))", "Field name 'x' specified more than once"); assertQueryFails("CREATE TABLE " + tableName + " AS SELECT cast(NULL AS row(a row(x int, \"X\" int))) col", "Field name 'x' specified more than once"); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_duplicated_field_names_", "(id int)")) { + try (TestTable table = newTrinoTable("test_duplicated_field_names_", "(id int)")) { assertQueryFails("ALTER TABLE " + table.getName() + " ADD COLUMN col row(x int, \"X\" int)", ".* Field name 'x' specified more than once"); assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN col row(\"X\" int)"); @@ -4992,7 +4942,7 @@ void testRegisterTableAccessControl() @Test public void testMetastoreAfterCreateTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int) COMMENT 'test comment'")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int) COMMENT 'test comment'")) { assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .contains( entry("comment", "test comment"), @@ -5004,7 +4954,7 @@ public void testMetastoreAfterCreateTable() @Test public void testMetastoreAfterCreateOrReplaceTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int) COMMENT 'test comment'")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int) COMMENT 'test comment'")) { assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + "(new_col varchar) COMMENT 'new comment'"); assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .contains( @@ -5017,7 +4967,7 @@ public void testMetastoreAfterCreateOrReplaceTable() @Test public void testMetastoreAfterCreateTableAsSelect() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "COMMENT 'test comment' AS SELECT 1 col")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "COMMENT 'test comment' AS SELECT 1 col")) { assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .contains( entry("comment", "test comment"), @@ -5029,7 +4979,7 @@ public void testMetastoreAfterCreateTableAsSelect() @Test public void testMetastoreAfterCreateOrReplaceTableAsSelect() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "COMMENT 'test comment' AS SELECT 1 col")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "COMMENT 'test comment' AS SELECT 1 col")) { assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + " COMMENT 'new comment' AS SELECT 'test' new_col", 1); assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .contains( @@ -5042,7 +4992,7 @@ public void testMetastoreAfterCreateOrReplaceTableAsSelect() @Test public void testMetastoreAfterCommentTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int)")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int)")) { assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .doesNotContainKey("comment") .contains( @@ -5061,7 +5011,7 @@ public void testMetastoreAfterCommentTable() @Test public void testMetastoreAfterCommentColumn() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int COMMENT 'test comment')")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int COMMENT 'test comment')")) { assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .doesNotContainKey("comment") .contains( @@ -5081,7 +5031,7 @@ public void testMetastoreAfterCommentColumn() public void testMetastoreAfterAlterColumn() { // Use 'name' column mapping mode to allow renaming columns - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int NOT NULL) WITH (column_mapping_mode = 'name')")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int NOT NULL) WITH (column_mapping_mode = 'name')")) { Map initialParameters = metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters(); assertThat(initialParameters) .doesNotContainKey("comment") @@ -5142,7 +5092,7 @@ public void testMetastoreAfterAlterColumn() @Test public void testMetastoreAfterSetTableProperties() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int)")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int)")) { assertUpdate("ALTER TABLE " + table.getName() + " SET PROPERTIES change_data_feed_enabled = true"); assertEventually(() -> assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .contains( @@ -5154,7 +5104,7 @@ public void testMetastoreAfterSetTableProperties() @Test public void testMetastoreAfterOptimize() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int)")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int)")) { assertUpdate("ALTER TABLE " + table.getName() + " EXECUTE optimize"); assertEventually(() -> assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .contains( @@ -5166,7 +5116,7 @@ public void testMetastoreAfterOptimize() @Test public void testMetastoreAfterRegisterTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int) COMMENT 'test comment'")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int) COMMENT 'test comment'")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 1", 1); String tableLocation = metastore.getTable(SCHEMA, table.getName()).orElseThrow().getStorage().getLocation(); metastore.dropTable(SCHEMA, table.getName(), false); @@ -5183,7 +5133,7 @@ public void testMetastoreAfterRegisterTable() @Test public void testMetastoreAfterCreateTableRemotely() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int) COMMENT 'test comment'")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int) COMMENT 'test comment'")) { Table metastoreTable = metastore.getTable(SCHEMA, table.getName()).orElseThrow(); metastore.dropTable(SCHEMA, table.getName(), false); @@ -5211,7 +5161,7 @@ public void testMetastoreAfterDataManipulation() { String schemaString = "{\"type\":\"struct\",\"fields\":[{\"name\":\"col\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]}"; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int)")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int)")) { assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .contains(entry("trino_last_transaction_version", "0"), entry("trino_metadata_schema_string", schemaString)); @@ -5245,7 +5195,7 @@ public void testMetastoreAfterTruncateTable() { String schemaString = "{\"type\":\"struct\",\"fields\":[{\"name\":\"col\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]}"; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "AS SELECT 1 col")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "AS SELECT 1 col")) { assertThat(metastore.getTable(SCHEMA, table.getName()).orElseThrow().getParameters()) .contains(entry("trino_last_transaction_version", "0"), entry("trino_metadata_schema_string", schemaString)); 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 1263d32ce54b..1bace334c40c 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 @@ -18,7 +18,7 @@ import io.trino.plugin.deltalake.transactionlog.AddFileEntry; import io.trino.plugin.deltalake.transactionlog.TransactionLogAccess; import io.trino.plugin.deltalake.transactionlog.statistics.DeltaLakeFileStatistics; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.spi.type.DateType; import io.trino.spi.type.DecimalType; import io.trino.spi.type.DoubleType; @@ -68,7 +68,7 @@ protected QueryRunner createQueryRunner() throws Exception { this.bucketName = "delta-test-create-table-statistics-" + randomNameSuffix(); - HiveMinioDataLake hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + Hive3MinioDataLake hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); return DeltaLakeQueryRunner.builder() 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 d616b3ec2045..f854110453ed 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 @@ -14,7 +14,7 @@ package io.trino.plugin.deltalake; import com.google.common.collect.ImmutableSet; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; import org.junit.jupiter.api.Test; @@ -31,13 +31,13 @@ public class TestDeltaLakeDelete extends AbstractTestQueryFramework { private final String bucketName = "test-delta-lake-connector-test-" + randomNameSuffix(); - private HiveMinioDataLake hiveMinioDataLake; + private Hive3MinioDataLake hiveMinioDataLake; @Override protected QueryRunner createQueryRunner() throws Exception { - hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); return DeltaLakeQueryRunner.builder() 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 ab566111784d..4bfe9a7cd8f6 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 @@ -22,7 +22,7 @@ import io.trino.metadata.QualifiedObjectName; import io.trino.metadata.Split; import io.trino.metadata.TableHandle; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.security.AllowAllAccessControl; import io.trino.spi.QueryId; import io.trino.spi.connector.ColumnHandle; @@ -60,14 +60,14 @@ public class TestDeltaLakeDynamicFiltering extends AbstractTestQueryFramework { private final String bucketName = "delta-lake-test-dynamic-filtering-" + randomNameSuffix(); - private HiveMinioDataLake hiveMinioDataLake; + private Hive3MinioDataLake hiveMinioDataLake; @Override protected QueryRunner createQueryRunner() throws Exception { verify(new DynamicFilterConfig().isEnableDynamicFiltering(), "this class assumes dynamic filtering is enabled by default"); - hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); QueryRunner queryRunner = DeltaLakeQueryRunner.builder() 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 7460919d0bfa..98230e78aa5d 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 @@ -21,9 +21,9 @@ import io.trino.Session; import io.trino.SystemSessionProperties; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.Table; import io.trino.plugin.deltalake.metastore.DeltaLakeTableMetadataScheduler; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; @@ -137,8 +137,9 @@ public void testCreateOrReplaceTable() assertFileSystemAccesses("CREATE OR REPLACE TABLE test_create_or_replace (id VARCHAR, age INT)", ImmutableMultiset.builder() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "OutputFile.createOrOverwrite")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.exists")) .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "InputFile.exists")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) @@ -167,8 +168,9 @@ public void testCreateOrReplaceTableAsSelect() .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "InputFile.newStream")) .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "OutputFile.createOrOverwrite")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "OutputFile.createOrOverwrite")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.exists")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .add(new FileOperation(DATA, "no partition", "OutputFile.create")) @@ -196,7 +198,10 @@ public void testReadUnpartitionedTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .addCopies(new FileOperation(DATA, "no partition", "InputFile.newInput"), 2) .build()); @@ -209,7 +214,10 @@ public void testReadUnpartitionedTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "InputFile.newStream")) .addCopies(new FileOperation(DATA, "no partition", "InputFile.newInput"), 2) .build()); @@ -233,7 +241,7 @@ public void testReadTableCheckpointInterval() .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .addCopies(new FileOperation(CHECKPOINT, "00000000000000000002.checkpoint.parquet", "InputFile.length"), 2) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query .addCopies(new FileOperation(CHECKPOINT, "00000000000000000002.checkpoint.parquet", "InputFile.newInput"), 2) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .addCopies(new FileOperation(DATA, "no partition", "InputFile.newInput"), 2) .build()); @@ -256,7 +264,7 @@ public void testReadPartitionTableWithCheckpointFiltering() .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .addCopies(new FileOperation(CHECKPOINT, "00000000000000000002.checkpoint.parquet", "InputFile.length"), 2) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query .addCopies(new FileOperation(CHECKPOINT, "00000000000000000002.checkpoint.parquet", "InputFile.newInput"), 2) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .addCopies(new FileOperation(DATA, "key=p1/", "InputFile.newInput"), 2) .addCopies(new FileOperation(DATA, "key=p2/", "InputFile.newInput"), 2) .build()); @@ -283,7 +291,10 @@ public void testReadWholePartition() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .addCopies(new FileOperation(DATA, "key=p1/", "InputFile.newInput"), 2) .addCopies(new FileOperation(DATA, "key=p2/", "InputFile.newInput"), 2) .build()); @@ -297,7 +308,10 @@ public void testReadWholePartition() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "InputFile.newStream")) .addCopies(new FileOperation(DATA, "key=p1/", "InputFile.newInput"), 2) .addCopies(new FileOperation(DATA, "key=p2/", "InputFile.newInput"), 2) @@ -312,7 +326,10 @@ public void testReadWholePartition() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "InputFile.newStream")) .build()); @@ -325,7 +342,10 @@ public void testReadWholePartition() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .build()); // Read partition and synthetic columns @@ -337,7 +357,10 @@ public void testReadWholePartition() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "InputFile.newStream")) .build()); @@ -350,7 +373,10 @@ public void testReadWholePartition() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .build()); assertUpdate("DROP TABLE test_read_part_key"); @@ -383,7 +409,9 @@ public void testReadWholePartitionSplittableFile() .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "InputFile.newStream")) .build()); @@ -396,7 +424,9 @@ public void testReadWholePartitionSplittableFile() .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) .build()); assertUpdate("DROP TABLE test_read_whole_splittable_file"); @@ -417,7 +447,8 @@ public void testSelfJoin() ImmutableMultiset.builder() .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "InputFile.newStream")) .addCopies(new FileOperation(DATA, "no partition", "InputFile.newInput"), 2) .build()); @@ -438,6 +469,7 @@ public void testSelectFromVersionedTable() ImmutableMultiset.builder() .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.exists")) .build()); assertFileSystemAccesses("SELECT * FROM " + tableName + " FOR VERSION AS OF 1", @@ -445,6 +477,8 @@ public void testSelectFromVersionedTable() .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.exists")) .add(new FileOperation(DATA, "no partition", "InputFile.newInput")) .build()); @@ -454,6 +488,9 @@ public void testSelectFromVersionedTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.exists")) .addCopies(new FileOperation(DATA, "no partition", "InputFile.newInput"), 2) .build()); @@ -469,6 +506,15 @@ public void testSelectFromVersionedTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000006.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000007.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000008.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000006.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000007.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000008.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000008.json", "InputFile.exists")) .addCopies(new FileOperation(DATA, "no partition", "InputFile.newInput"), 8) .build()); @@ -480,6 +526,9 @@ public void testSelectFromVersionedTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000011.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000012.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000013.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000011.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000012.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000013.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000013.json", "InputFile.exists")) .addCopies(new FileOperation(DATA, "no partition", "InputFile.newInput"), 13) .build()); @@ -499,6 +548,9 @@ public void testSelectFromVersionedTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000021.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000022.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000023.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000021.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000022.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000023.json", "InputFile.length")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000023.json", "InputFile.exists")) .addCopies(new FileOperation(DATA, "no partition", "InputFile.newInput"), 23) .build()); @@ -527,7 +579,10 @@ public void testDeleteWholePartition() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.exists")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "OutputFile.createOrOverwrite")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .build()); assertUpdate("DROP TABLE test_delete_part_key"); @@ -553,7 +608,10 @@ public void testDeleteWholeTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.exists")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "OutputFile.createOrOverwrite")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .build()); assertUpdate("DROP TABLE test_delete_whole_table"); @@ -578,7 +636,11 @@ public void testDeleteWithNonPartitionFilter() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.exists")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "OutputFile.createOrOverwrite")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.length")) .addCopies(new FileOperation(DATA, "key=domain1/", "InputFile.newInput"), 2) .add(new FileOperation(DATA, "key=domain1/", "InputFile.length")) .add(new FileOperation(DATA, "key=domain1/", "OutputFile.create")) @@ -603,7 +665,12 @@ public void testHistorySystemTable() .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream"), 2) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream"), 2) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.newStream"), 2) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.newStream")) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.length"), 2) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.length")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .build()); @@ -614,7 +681,12 @@ public void testHistorySystemTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream"), 2) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length"), 2) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.length")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .build()); @@ -625,7 +697,12 @@ public void testHistorySystemTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.newStream"), 2) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.length"), 2) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.length")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .build()); @@ -636,7 +713,12 @@ public void testHistorySystemTable() .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream"), 2) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream"), 2) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.newStream"), 2) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.length"), 2) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.length")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .build()); @@ -647,7 +729,12 @@ public void testHistorySystemTable() .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream"), 2) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length"), 2) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.length")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .build()); @@ -658,7 +745,12 @@ public void testHistorySystemTable() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.length")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .build()); } @@ -688,7 +780,14 @@ public void testTableChangesFileSystemAccess() .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.newStream"), 2) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.newStream"), 2) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000006.json", "InputFile.newStream"), 2) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000007.json", "InputFile.newStream")) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000004.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000005.json", "InputFile.length"), 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000006.json", "InputFile.length"), 2) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000007.json", "InputFile.length")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .add(new FileOperation(CDF_DATA, "key=domain1/", "InputFile.newInput")) .addCopies(new FileOperation(CDF_DATA, "key=domain2/", "InputFile.newInput"), cdfFilesForDomain2) @@ -738,9 +837,12 @@ private void testInformationSchemaColumns(boolean removeCachedProperties) ImmutableMultiset.builder() .addCopies(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream"), tables * 2) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream"), tables * 2) - .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream"), tables * 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream"), tables) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream"), tables) - .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream"), tables) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length"), tables * 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length"), tables * 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length"), tables) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length"), tables) .build() : ImmutableMultiset.builder() .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.exists"), tables) @@ -774,7 +876,10 @@ private void testInformationSchemaColumns(boolean removeCachedProperties) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .build()); // Pointed lookup with LIKE predicate (as if unintentional) @@ -793,7 +898,10 @@ private void testInformationSchemaColumns(boolean removeCachedProperties) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .build()); for (int i = 0; i < tables; i++) { @@ -842,9 +950,12 @@ private void testSystemMetadataTableComments(boolean removeCachedProperties) ImmutableMultiset.builder() .addCopies(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream"), tables * 2) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream"), tables * 2) - .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream"), tables * 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream"), tables) .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream"), tables) - .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream"), tables) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length"), tables * 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length"), tables * 2) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length"), tables) + .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length"), tables) .build() : ImmutableMultiset.builder() .addCopies(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.exists"), tables) @@ -887,7 +998,10 @@ private void testSystemMetadataTableComments(boolean removeCachedProperties) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .build()); // Pointed lookup with LIKE predicate (as if unintentional) @@ -929,7 +1043,8 @@ public void testReadMultipartCheckpoint() .addCopies(new FileOperation(CHECKPOINT, "00000000000000000006.checkpoint.0000000002.0000000002.parquet", "InputFile.length"), 2) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query .addCopies(new FileOperation(CHECKPOINT, "00000000000000000006.checkpoint.0000000002.0000000002.parquet", "InputFile.newInput"), 2) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000007.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000008.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000007.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000008.json", "InputFile.length")) .addCopies(new FileOperation(DATA, "no partition", "InputFile.newInput"), 7) .build()); } @@ -947,7 +1062,7 @@ public void testV2CheckpointJson() .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .add(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.73a4ddb8-2bfc-40d8-b09f-1b6a0abdfb04.json", "InputFile.length")) .add(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.73a4ddb8-2bfc-40d8-b09f-1b6a0abdfb04.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) // TransactionLogTail.getEntriesFromJson access non-existing file as end of tail + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) // TransactionLogTail.getEntriesFromJson access non-existing file as end of tail .build()); assertFileSystemAccesses("SELECT * FROM " + tableName, @@ -957,7 +1072,7 @@ public void testV2CheckpointJson() .addCopies(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.73a4ddb8-2bfc-40d8-b09f-1b6a0abdfb04.json", "InputFile.newStream"), 2) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query .add(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.0000000001.0000000001.90cf4e21-dbaa-41d6-8ae5-6709cfbfbfe0.parquet", "InputFile.length")) .add(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.0000000001.0000000001.90cf4e21-dbaa-41d6-8ae5-6709cfbfbfe0.parquet", "InputFile.newInput")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) // TransactionLogTail.getEntriesFromJson access non-existing file as end of tail + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) // TransactionLogTail.getEntriesFromJson access non-existing file as end of tail .add(new FileOperation(DATA, "no partition", "InputFile.newInput")) .build()); @@ -977,7 +1092,7 @@ public void testV2CheckpointParquet() .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .addCopies(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.156b3304-76b2-49c3-a9a1-626f07df27c9.parquet", "InputFile.length"), 2) .add(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.156b3304-76b2-49c3-a9a1-626f07df27c9.parquet", "InputFile.newInput")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) // TransactionLogTail.getEntriesFromJson access non-existing file as end of tail + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) // TransactionLogTail.getEntriesFromJson access non-existing file as end of tail .build()); assertFileSystemAccesses("SELECT * FROM " + tableName, @@ -987,7 +1102,7 @@ public void testV2CheckpointParquet() .addCopies(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.156b3304-76b2-49c3-a9a1-626f07df27c9.parquet", "InputFile.newInput"), 2) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query .add(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.0000000001.0000000001.03288d7e-af16-44ed-829c-196064a71812.parquet", "InputFile.length")) .add(new FileOperation(CHECKPOINT, "00000000000000000001.checkpoint.0000000001.0000000001.03288d7e-af16-44ed-829c-196064a71812.parquet", "InputFile.newInput")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) // TransactionLogTail.getEntriesFromJson access non-existing file as end of tail + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) // TransactionLogTail.getEntriesFromJson access non-existing file as end of tail .add(new FileOperation(DATA, "no partition", "InputFile.newInput")) .build()); @@ -1006,7 +1121,10 @@ public void testDeletionVectors() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) .add(new FileOperation(DELETION_VECTOR, "deletion_vector_a52eda8c-0a57-4636-814b-9c165388f7ca.bin", "InputFile.newInput")) .add(new FileOperation(DATA, "no partition", "InputFile.newInput")) @@ -1017,7 +1135,10 @@ public void testDeletionVectors() .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.newStream")) .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.newStream")) - .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.newStream")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000000.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000001.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000002.json", "InputFile.length")) + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", "InputFile.length")) .add(new FileOperation(STARBURST_EXTENDED_STATS_JSON, "extendeded_stats.json", "InputFile.newStream")) .add(new FileOperation(TRINO_EXTENDED_STATS_JSON, "extended_stats.json", "InputFile.newStream")) .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", "InputFile.newStream")) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeFlushMetadataCacheProcedure.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeFlushMetadataCacheProcedure.java index 61d73d098800..615c6ab50f81 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeFlushMetadataCacheProcedure.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeFlushMetadataCacheProcedure.java @@ -14,7 +14,7 @@ package io.trino.plugin.deltalake; import io.trino.metastore.HiveMetastore; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; @@ -39,11 +39,11 @@ public class TestDeltaLakeFlushMetadataCacheProcedure protected QueryRunner createQueryRunner() throws Exception { - HiveMinioDataLake hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName, HIVE3_IMAGE)); + Hive3MinioDataLake hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName, HIVE3_IMAGE)); hiveMinioDataLake.start(); metastore = new BridgingHiveMetastore( testingThriftHiveMetastoreBuilder() - .metastoreClient(hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint()) + .metastoreClient(hiveMinioDataLake.getHiveMetastoreEndpoint()) .build(this::closeAfterClass)); return DeltaLakeQueryRunner.builder("default") 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 fbecc7fa09be..f2527040d271 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 @@ -30,14 +30,14 @@ import io.trino.hdfs.HdfsEnvironment; import io.trino.hdfs.TrinoHdfsFileSystemStats; import io.trino.metastore.Database; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.plugin.deltalake.metastore.DeltaLakeMetastore; import io.trino.plugin.deltalake.metastore.DeltaLakeMetastoreModule; import io.trino.plugin.deltalake.metastore.HiveMetastoreBackedDeltaLakeMetastore; import io.trino.plugin.deltalake.transactionlog.MetadataEntry; import io.trino.plugin.deltalake.transactionlog.ProtocolEntry; import io.trino.plugin.hive.NodeVersion; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; import io.trino.spi.NodeManager; import io.trino.spi.PageIndexerFactory; import io.trino.spi.TrinoException; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMinioAndLockBasedSynchronizerSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMinioAndLockBasedSynchronizerSmokeTest.java index a3e4e6842e16..9bb7240b94a1 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMinioAndLockBasedSynchronizerSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMinioAndLockBasedSynchronizerSmokeTest.java @@ -19,8 +19,8 @@ import io.airlift.json.ObjectMapperProvider; import io.airlift.units.Duration; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.deltalake.transactionlog.writer.S3LockBasedTransactionLogSynchronizer; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.tpch.TpchPlugin; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.DistributedQueryRunner; 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 d5c20b7dda9d..2ebc98f68e5b 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 @@ -15,7 +15,6 @@ import com.google.common.collect.ImmutableMap; import io.airlift.bootstrap.ApplicationConfigurationException; -import io.trino.plugin.hive.HiveConfig; import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; @@ -165,7 +164,7 @@ 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", + "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-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeProjectionPushdownPlans.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeProjectionPushdownPlans.java index 93f0741f7ae0..755a065daea2 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeProjectionPushdownPlans.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeProjectionPushdownPlans.java @@ -23,7 +23,7 @@ import io.trino.metadata.TestingFunctionResolution; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.spi.connector.ColumnHandle; import io.trino.spi.function.OperatorType; import io.trino.spi.predicate.Domain; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedHiveMetastoreWithViews.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedHiveMetastoreWithViews.java index bef79413f6fd..bcd9da3bd217 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedHiveMetastoreWithViews.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedHiveMetastoreWithViews.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.trino.plugin.hive.TestingHivePlugin; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; import org.junit.jupiter.api.AfterAll; @@ -35,14 +35,14 @@ public class TestDeltaLakeSharedHiveMetastoreWithViews extends AbstractTestQueryFramework { private final String bucketName = "delta-lake-shared-hive-with-views-" + randomNameSuffix(); - private HiveMinioDataLake hiveMinioDataLake; + private Hive3MinioDataLake hiveMinioDataLake; private String schema; @Override protected QueryRunner createQueryRunner() throws Exception { - this.hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + this.hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); this.hiveMinioDataLake.start(); QueryRunner queryRunner = DeltaLakeQueryRunner.builder() @@ -54,7 +54,7 @@ protected QueryRunner createQueryRunner() queryRunner.installPlugin(new TestingHivePlugin(queryRunner.getCoordinator().getBaseDataDir().resolve("hive_data"))); queryRunner.createCatalog("hive", "hive", ImmutableMap.builder() .put("hive.metastore", "thrift") - .put("hive.metastore.uri", hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint().toString()) + .put("hive.metastore.uri", hiveMinioDataLake.getHiveMetastoreEndpoint().toString()) .put("fs.hadoop.enabled", "false") .put("fs.native-s3.enabled", "true") .put("s3.aws-access-key", MINIO_ACCESS_KEY) @@ -67,7 +67,7 @@ protected QueryRunner createQueryRunner() schema = queryRunner.getDefaultSession().getSchema().orElseThrow(); queryRunner.execute("CREATE TABLE hive." + schema + ".hive_table (a_integer integer)"); - hiveMinioDataLake.getHiveHadoop().runOnHive("CREATE VIEW " + schema + ".hive_view AS SELECT * FROM " + schema + ".hive_table"); + hiveMinioDataLake.runOnHive("CREATE VIEW " + schema + ".hive_view AS SELECT * FROM " + schema + ".hive_table"); queryRunner.execute("CREATE TABLE delta." + schema + ".delta_table (a_varchar varchar)"); return queryRunner; @@ -82,7 +82,7 @@ protected QueryRunner createQueryRunner() public void cleanup() { assertQuerySucceeds("DROP TABLE IF EXISTS hive." + schema + ".hive_table"); - hiveMinioDataLake.getHiveHadoop().runOnHive("DROP VIEW IF EXISTS " + schema + ".hive_view"); + hiveMinioDataLake.runOnHive("DROP VIEW IF EXISTS " + schema + ".hive_view"); assertQuerySucceeds("DROP TABLE IF EXISTS delta." + schema + ".delta_table"); assertQuerySucceeds("DROP SCHEMA IF EXISTS hive." + schema); } 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 779318497889..a5ddd9368b7b 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 @@ -22,6 +22,7 @@ import io.trino.filesystem.cache.DefaultCachingHostAddressProvider; import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.filesystem.memory.MemoryFileSystemFactory; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.base.metrics.FileFormatDataSourceStats; import io.trino.plugin.deltalake.metastore.DeltaLakeTableMetadataScheduler; import io.trino.plugin.deltalake.metastore.file.DeltaLakeFileMetastoreTableOperationsProvider; @@ -41,7 +42,6 @@ import io.trino.plugin.deltalake.transactionlog.writer.TransactionLogWriterFactory; import io.trino.plugin.hive.HiveTransactionHandle; import io.trino.plugin.hive.NodeVersion; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.parquet.ParquetReaderConfig; import io.trino.plugin.hive.parquet.ParquetWriterConfig; import io.trino.spi.SplitWeight; @@ -230,7 +230,6 @@ public Stream getActiveFiles( new TransactionLogSynchronizerManager(ImmutableMap.of(), new NoIsolationSynchronizer(hdfsFileSystemFactory))), new TestingNodeManager(), checkpointWriterManager, - DeltaLakeRedirectionsProvider.NOOP, new CachingExtendedStatisticsAccess(new MetaDirStatisticsAccess(HDFS_FILE_SYSTEM_FACTORY, new JsonCodecFactory().jsonCodec(ExtendedStatistics.class))), true, new NodeVersion("test_version"), 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 8deee1b2a95e..37f0fdfab526 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 @@ -24,8 +24,8 @@ 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 io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; -import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @TestInstance(PER_CLASS) @@ -45,7 +45,7 @@ protected QueryRunner createQueryRunner() schema = "test_tables_with_custom_location" + randomNameSuffix(); return DeltaLakeQueryRunner.builder(schema) .addDeltaProperty("hive.metastore", "glue") - .addDeltaProperty("hive.metastore.glue.region", requireNonNull(System.getenv("AWS_REGION"), "AWS_REGION is null")) + .addDeltaProperty("hive.metastore.glue.region", requireEnv("AWS_REGION")) .addDeltaProperty("hive.metastore.glue.default-warehouse-dir", warehouseDir.toUri().toString()) .build(); } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeUpdate.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeUpdate.java index 857f15b0c2c0..6c1cab6c0982 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeUpdate.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeUpdate.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.deltalake; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; import org.junit.jupiter.api.Test; @@ -35,7 +35,7 @@ public TestDeltaLakeUpdate() protected QueryRunner createQueryRunner() throws Exception { - HiveMinioDataLake hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + Hive3MinioDataLake hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); return DeltaLakeQueryRunner.builder() 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 0d6bf3610430..e6b909b93aa0 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 @@ -16,7 +16,7 @@ import com.google.common.collect.ContiguousSet; import io.trino.Session; import io.trino.operator.OperatorStats; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.spi.QueryId; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.MaterializedResult; @@ -48,13 +48,13 @@ public class TestPredicatePushdown */ private final TableResource testTable = new TableResource("custkey_15rowgroups"); - private HiveMinioDataLake hiveMinioDataLake; + private Hive3MinioDataLake hiveMinioDataLake; @Override protected QueryRunner createQueryRunner() throws Exception { - hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); return DeltaLakeQueryRunner.builder() 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 fe3acd47323a..6360b306440b 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 @@ -253,7 +253,7 @@ public void testPartitionPruningWithExpression() /** * Test that partition filter that cannot be converted to a {@link io.trino.spi.predicate.Domain} - * gets applied (and not forgotten) when there is another, Domain-convertable filter. + * gets applied (and not forgotten) when there is another, Domain-convertible filter. *

* In the past, that caused a significant decrease in the connector's performance. */ 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 3593fc783ef7..cba08151ff37 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 @@ -27,10 +27,10 @@ import io.trino.plugin.deltalake.transactionlog.AddFileEntry; 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.TableSnapshot; import io.trino.plugin.deltalake.transactionlog.TransactionLogAccess; import io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointSchemaManager; +import io.trino.plugin.deltalake.transactionlog.checkpoint.MetadataAndProtocolEntries; import io.trino.plugin.deltalake.transactionlog.statistics.DeltaLakeFileStatistics; import io.trino.plugin.hive.parquet.ParquetReaderConfig; import io.trino.plugin.hive.parquet.ParquetWriterConfig; @@ -103,14 +103,6 @@ public class TestTransactionLogAccess "age=28/part-00000-40dd1707-1d42-4328-a59a-21f5c945fe60.c000.snappy.parquet", "age=29/part-00000-3794c463-cb0c-4beb-8d07-7cc1e3b5920f.c000.snappy.parquet"); - private static final Set EXPECTED_REMOVE_ENTRIES = ImmutableSet.of( - new RemoveFileEntry("age=30/part-00000-7e43a3c3-ea26-4ae7-8eac-8f60cbb4df03.c000.snappy.parquet", null, 1579190163932L, false, Optional.empty()), - new RemoveFileEntry("age=30/part-00000-72a56c23-01ba-483a-9062-dd0accc86599.c000.snappy.parquet", null, 1579190163932L, false, Optional.empty()), - new RemoveFileEntry("age=42/part-00000-951068bd-bcf4-4094-bb94-536f3c41d31f.c000.snappy.parquet", null, 1579190155406L, false, Optional.empty()), - new RemoveFileEntry("age=25/part-00000-609e34b1-5466-4dbc-a780-2708166e7adb.c000.snappy.parquet", null, 1579190163932L, false, Optional.empty()), - new RemoveFileEntry("age=42/part-00000-6aed618a-2beb-4edd-8466-653e67a9b380.c000.snappy.parquet", null, 1579190155406L, false, Optional.empty()), - new RemoveFileEntry("age=42/part-00000-b82d8859-84a0-4f05-872c-206b07dd54f0.c000.snappy.parquet", null, 1579190163932L, false, Optional.empty())); - private final TestingTelemetry testingTelemetry = TestingTelemetry.create("transaction-log-access"); private final TracingFileSystemFactory tracingFileSystemFactory = new TracingFileSystemFactory(testingTelemetry.getTracer(), new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS)); @@ -322,18 +314,6 @@ public void testAddRemoveAdd() } } - @Test - public void testGetRemoveEntries() - throws Exception - { - setupTransactionLogAccessFromResources("person", "databricks73/person"); - - try (Stream removeEntries = transactionLogAccess.getRemoveEntries(SESSION, tableSnapshot)) { - Set removedEntries = removeEntries.collect(Collectors.toSet()); - assertThat(removedEntries).isEqualTo(EXPECTED_REMOVE_ENTRIES); - } - } - @Test public void testAllGetMetadataEntry() throws Exception @@ -359,6 +339,35 @@ private void testAllGetMetadataEntry(String tableName, String resourcePath) assertThat(format.provider()).isEqualTo("parquet"); } + @Test + void testGetMetadataAndProtocolEntry() + throws Exception + { + testGetMetadataAndProtocolEntry("person", "databricks73/person"); + testGetMetadataAndProtocolEntry("person_without_last_checkpoint", "databricks73/person_without_last_checkpoint"); + testGetMetadataAndProtocolEntry("person_without_old_jsons", "databricks73/person_without_old_jsons"); + testGetMetadataAndProtocolEntry("person_without_checkpoints", "databricks73/person_without_checkpoints"); + } + + private void testGetMetadataAndProtocolEntry(String tableName, String resourcePath) + throws Exception + { + setupTransactionLogAccessFromResources(tableName, resourcePath); + + transactionLogAccess.getMetadataAndProtocolEntry(SESSION, tableSnapshot); + MetadataAndProtocolEntries logEntries = transactionLogAccess.getMetadataAndProtocolEntry(SESSION, tableSnapshot); + + MetadataEntry metadataEntry = logEntries.metadata().orElseThrow(); + assertThat(metadataEntry.getOriginalPartitionColumns()).containsOnly("age"); + MetadataEntry.Format format = metadataEntry.getFormat(); + assertThat(format.options().keySet()).isEmpty(); + assertThat(format.provider()).isEqualTo("parquet"); + + ProtocolEntry protocolEntry = logEntries.protocol().orElseThrow(); + assertThat(protocolEntry.minReaderVersion()).isEqualTo(1); + assertThat(protocolEntry.minWriterVersion()).isEqualTo(2); + } + @Test public void testAllGetActiveAddEntries() throws Exception @@ -384,29 +393,6 @@ private void testAllGetActiveAddEntries(String tableName, String resourcePath) } } - @Test - public void testAllGetRemoveEntries() - throws Exception - { - testAllGetRemoveEntries("person", "databricks73/person"); - testAllGetRemoveEntries("person_without_last_checkpoint", "databricks73/person_without_last_checkpoint"); - testAllGetRemoveEntries("person_without_old_jsons", "databricks73/person_without_old_jsons"); - testAllGetRemoveEntries("person_without_checkpoints", "databricks73/person_without_checkpoints"); - } - - private void testAllGetRemoveEntries(String tableName, String resourcePath) - throws Exception - { - setupTransactionLogAccessFromResources(tableName, resourcePath); - - try (Stream removeEntries = transactionLogAccess.getRemoveEntries(SESSION, tableSnapshot)) { - Set removedPaths = removeEntries.map(RemoveFileEntry::path).collect(Collectors.toSet()); - Set expectedPaths = EXPECTED_REMOVE_ENTRIES.stream().map(RemoveFileEntry::path).collect(Collectors.toSet()); - - assertThat(removedPaths).isEqualTo(expectedPaths); - } - } - @Test public void testAllGetProtocolEntries() throws Exception @@ -597,7 +583,8 @@ public void testIncrementalCacheUpdates() .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.newInput")) .add(new FileOperation("00000000000000000011.json", "InputFile.newStream")) - .add(new FileOperation("00000000000000000012.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.length")) + .add(new FileOperation("00000000000000000012.json", "InputFile.length")) .build()); copyTransactionLogEntry(12, 14, resourceDir, transactionLogDir); @@ -615,7 +602,9 @@ public void testIncrementalCacheUpdates() .add(new FileOperation("_last_checkpoint", "InputFile.newStream")) .addCopies(new FileOperation("00000000000000000012.json", "InputFile.newStream"), 2) .addCopies(new FileOperation("00000000000000000013.json", "InputFile.newStream"), 2) - .add(new FileOperation("00000000000000000014.json", "InputFile.newStream")) + .addCopies(new FileOperation("00000000000000000012.json", "InputFile.length"), 2) + .addCopies(new FileOperation("00000000000000000013.json", "InputFile.length"), 2) + .add(new FileOperation("00000000000000000014.json", "InputFile.length")) .build()); } @@ -777,7 +766,10 @@ public void testTableSnapshotsCacheDisabled() .add(new FileOperation("00000000000000000011.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000012.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000013.json", "InputFile.newStream")) - .add(new FileOperation("00000000000000000014.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.length")) + .add(new FileOperation("00000000000000000012.json", "InputFile.length")) + .add(new FileOperation("00000000000000000013.json", "InputFile.length")) + .add(new FileOperation("00000000000000000014.json", "InputFile.length")) .build()); // With the transaction log cache disabled, when loading the snapshot again, all the needed files will be opened again @@ -790,7 +782,10 @@ public void testTableSnapshotsCacheDisabled() .add(new FileOperation("00000000000000000011.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000012.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000013.json", "InputFile.newStream")) - .add(new FileOperation("00000000000000000014.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.length")) + .add(new FileOperation("00000000000000000012.json", "InputFile.length")) + .add(new FileOperation("00000000000000000013.json", "InputFile.length")) + .add(new FileOperation("00000000000000000014.json", "InputFile.length")) .build()); } @@ -826,7 +821,10 @@ public void testTableSnapshotsActiveDataFilesCache() .add(new FileOperation("00000000000000000011.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000012.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000013.json", "InputFile.newStream")) - .add(new FileOperation("00000000000000000014.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.length")) + .add(new FileOperation("00000000000000000012.json", "InputFile.length")) + .add(new FileOperation("00000000000000000013.json", "InputFile.length")) + .add(new FileOperation("00000000000000000014.json", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.newInput")) .build()); @@ -866,7 +864,10 @@ public void testFlushSnapshotAndActiveFileCache() .add(new FileOperation("00000000000000000011.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000012.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000013.json", "InputFile.newStream")) - .add(new FileOperation("00000000000000000014.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.length")) + .add(new FileOperation("00000000000000000012.json", "InputFile.length")) + .add(new FileOperation("00000000000000000013.json", "InputFile.length")) + .add(new FileOperation("00000000000000000014.json", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.newInput")) .build()); @@ -885,7 +886,10 @@ public void testFlushSnapshotAndActiveFileCache() .add(new FileOperation("00000000000000000011.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000012.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000013.json", "InputFile.newStream")) - .add(new FileOperation("00000000000000000014.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.length")) + .add(new FileOperation("00000000000000000012.json", "InputFile.length")) + .add(new FileOperation("00000000000000000013.json", "InputFile.length")) + .add(new FileOperation("00000000000000000014.json", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.newInput")) .build()); @@ -916,7 +920,10 @@ public void testTableSnapshotsActiveDataFilesCacheDisabled() .add(new FileOperation("00000000000000000011.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000012.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000013.json", "InputFile.newStream")) - .add(new FileOperation("00000000000000000014.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.length")) + .add(new FileOperation("00000000000000000012.json", "InputFile.length")) + .add(new FileOperation("00000000000000000013.json", "InputFile.length")) + .add(new FileOperation("00000000000000000014.json", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.newInput")) .build()); @@ -968,6 +975,16 @@ public void testLoadSnapshotWithEndVersion() .add(new FileOperation("00000000000000000007.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000008.json", "InputFile.newStream")) .add(new FileOperation("00000000000000000009.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000000.json", "InputFile.length")) + .add(new FileOperation("00000000000000000001.json", "InputFile.length")) + .add(new FileOperation("00000000000000000002.json", "InputFile.length")) + .add(new FileOperation("00000000000000000003.json", "InputFile.length")) + .add(new FileOperation("00000000000000000004.json", "InputFile.length")) + .add(new FileOperation("00000000000000000005.json", "InputFile.length")) + .add(new FileOperation("00000000000000000006.json", "InputFile.length")) + .add(new FileOperation("00000000000000000007.json", "InputFile.length")) + .add(new FileOperation("00000000000000000008.json", "InputFile.length")) + .add(new FileOperation("00000000000000000009.json", "InputFile.length")) .build()); setupTransactionLogAccess("person", getClass().getClassLoader().getResource("databricks73/person").toString(), new DeltaLakeConfig(), Optional.of(10L)); @@ -999,6 +1016,7 @@ public void testLoadSnapshotWithEndVersion() .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.length")) .add(new FileOperation("00000000000000000010.checkpoint.parquet", "InputFile.newInput")) .add(new FileOperation("00000000000000000011.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.length")) .build()); } 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 9e0c17aa8692..5211e96c5490 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 @@ -20,10 +20,10 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.trino.Session; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.Table; import io.trino.plugin.deltalake.DeltaLakeQueryRunner; import io.trino.plugin.deltalake.TestingDeltaLakeUtils; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.MetastoreMethod; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; @@ -408,7 +408,7 @@ public void testStoreMetastoreCommentTable() private void testStoreMetastoreCommentTable(boolean storeTableMetadata) { Session session = sessionWithStoreTableMetadata(storeTableMetadata); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int)")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int)")) { assertMetastoreInvocations(session, "COMMENT ON TABLE " + table.getName() + " IS 'test comment'", ImmutableMultiset.of(GET_TABLE), asyncInvocations(storeTableMetadata)); } } @@ -423,7 +423,7 @@ public void testStoreMetastoreCommentColumn() private void testStoreMetastoreCommentColumn(boolean storeTableMetadata) { Session session = sessionWithStoreTableMetadata(storeTableMetadata); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int COMMENT 'test comment')")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int COMMENT 'test comment')")) { assertMetastoreInvocations(session, "COMMENT ON COLUMN " + table.getName() + ".col IS 'new test comment'", ImmutableMultiset.of(GET_TABLE), asyncInvocations(storeTableMetadata)); } } @@ -440,7 +440,7 @@ private void testStoreMetastoreAlterColumn(boolean storeTableMetadata) Session session = sessionWithStoreTableMetadata(storeTableMetadata); // Use 'name' column mapping mode to allow renaming columns - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int NOT NULL) WITH (column_mapping_mode = 'name')")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int NOT NULL) WITH (column_mapping_mode = 'name')")) { assertMetastoreInvocations(session, "ALTER TABLE " + table.getName() + " ALTER COLUMN col DROP NOT NULL", ImmutableMultiset.of(GET_TABLE), asyncInvocations(storeTableMetadata)); assertMetastoreInvocations(session, "ALTER TABLE " + table.getName() + " ADD COLUMN new_col int COMMENT 'test comment'", ImmutableMultiset.of(GET_TABLE), asyncInvocations(storeTableMetadata)); assertMetastoreInvocations(session, "ALTER TABLE " + table.getName() + " RENAME COLUMN new_col TO renamed_col", ImmutableMultiset.of(GET_TABLE), asyncInvocations(storeTableMetadata)); @@ -460,7 +460,7 @@ public void testStoreMetastoreSetTableProperties() private void testStoreMetastoreSetTableProperties(boolean storeTableMetadata) { Session session = sessionWithStoreTableMetadata(storeTableMetadata); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int)")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int)")) { assertMetastoreInvocations(session, "ALTER TABLE " + table.getName() + " SET PROPERTIES change_data_feed_enabled = true", ImmutableMultiset.of(GET_TABLE), asyncInvocations(storeTableMetadata)); } } @@ -475,7 +475,7 @@ public void testStoreMetastoreOptimize() private void testStoreMetastoreOptimize(boolean storeTableMetadata) { Session session = sessionWithStoreTableMetadata(storeTableMetadata); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int)")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int)")) { assertMetastoreInvocations(session, "ALTER TABLE " + table.getName() + " EXECUTE optimize", ImmutableMultiset.of(GET_TABLE), asyncInvocations(storeTableMetadata)); } } @@ -494,7 +494,7 @@ private void testStoreMetastoreVacuum(boolean storeTableMetadata) .setCatalogSessionProperty(getSession().getCatalog().orElseThrow(), "vacuum_min_retention", "0s") .build(); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "AS SELECT 1 a")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "AS SELECT 1 a")) { assertUpdate("UPDATE " + table.getName() + " SET a = 2", 1); assertMetastoreInvocations( session, @@ -513,7 +513,7 @@ public void testStoreMetastoreRegisterTable() private void testStoreMetastoreRegisterTable(boolean storeTableMetadata) { Session session = sessionWithStoreTableMetadata(storeTableMetadata); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int) COMMENT 'test comment'")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int) COMMENT 'test comment'")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES 1", 1); String tableLocation = metastore.getTable(TPCH_SCHEMA, table.getName()).orElseThrow().getStorage().getLocation(); metastore.dropTable(TPCH_SCHEMA, table.getName(), false); @@ -537,7 +537,7 @@ private void testStoreMetastoreDataManipulation(boolean storeTableMetadata) Session session = sessionWithStoreTableMetadata(storeTableMetadata); String schemaString = "{\"type\":\"struct\",\"fields\":[{\"name\":\"col\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]}"; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "(col int)")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "(col int)")) { assertThat(metastore.getTable(TPCH_SCHEMA, table.getName()).orElseThrow().getParameters()) .contains(entry("trino_last_transaction_version", "0"), entry("trino_metadata_schema_string", schemaString)); @@ -563,7 +563,7 @@ public void testStoreMetastoreTruncateTable() private void testStoreMetastoreTruncateTable(boolean storeTableMetadata) { Session session = sessionWithStoreTableMetadata(storeTableMetadata); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_cache_metastore", "AS SELECT 1 col")) { + try (TestTable table = newTrinoTable("test_cache_metastore", "AS SELECT 1 col")) { assertMetastoreInvocations(session, "TRUNCATE TABLE " + table.getName(), ImmutableMultiset.of(GET_TABLE), asyncInvocations(storeTableMetadata)); } } 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 adea03218e84..07bf6000f02d 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,13 +18,13 @@ import com.google.inject.Scopes; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.plugin.deltalake.AllowDeltaLakeManagedTableRename; import io.trino.plugin.deltalake.MaxTableParameterLength; import io.trino.plugin.deltalake.metastore.file.DeltaLakeFileMetastoreTableOperationsProvider; import io.trino.plugin.hive.HideDeltaLakeTables; import io.trino.plugin.hive.metastore.CachingHiveMetastoreModule; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; import static java.util.Objects.requireNonNull; @@ -42,7 +42,7 @@ public TestingDeltaLakeMetastoreModule(HiveMetastore metastore) public void setup(Binder binder) { binder.bind(HiveMetastoreFactory.class).annotatedWith(RawHiveMetastoreFactory.class).toInstance(HiveMetastoreFactory.ofInstance(metastore)); - install(new CachingHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule()); binder.bind(DeltaLakeTableOperationsProvider.class).to(DeltaLakeFileMetastoreTableOperationsProvider.class).in(Scopes.SINGLETON); binder.bind(Key.get(boolean.class, HideDeltaLakeTables.class)).toInstance(false); 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 9ffae4586f88..3922dc250f73 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 @@ -28,6 +28,7 @@ import io.trino.metastore.Column; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.Table; import io.trino.plugin.base.session.SessionPropertiesProvider; @@ -36,7 +37,6 @@ import io.trino.plugin.deltalake.DeltaLakeModule; import io.trino.plugin.deltalake.metastore.DeltaLakeMetastoreModule; import io.trino.plugin.hive.NodeVersion; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.NodeManager; import io.trino.spi.PageIndexerFactory; import io.trino.spi.TrinoException; 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 d190234723a6..d67d92431355 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 @@ -25,8 +25,8 @@ 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 io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; -import static java.util.Objects.requireNonNull; public class TestDeltaLakeRegisterTableProcedureWithGlue extends BaseDeltaLakeRegisterTableProcedureTest @@ -44,7 +44,7 @@ protected QueryRunner createQueryRunner() schema = "test_delta_lake_register_table" + randomNameSuffix(); return DeltaLakeQueryRunner.builder(schema) .addDeltaProperty("hive.metastore", "glue") - .addDeltaProperty("hive.metastore.glue.region", requireNonNull(System.getenv("AWS_REGION"), "AWS_REGION is null")) + .addDeltaProperty("hive.metastore.glue.region", requireEnv("AWS_REGION")) .addDeltaProperty("hive.metastore.glue.default-warehouse-dir", warehouseDir.toUri().toString()) .addDeltaProperty("delta.unique-table-location", "true") .addDeltaProperty("delta.register-table-procedure.enabled", "true") 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 f5b694bbf34c..a87d8e960d1e 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 @@ -30,9 +30,9 @@ 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 io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @TestInstance(PER_CLASS) @@ -52,7 +52,7 @@ protected QueryRunner createQueryRunner() schema = "test_delta_lake_glue_views_" + randomNameSuffix(); return DeltaLakeQueryRunner.builder(schema) .addDeltaProperty("hive.metastore", "glue") - .addDeltaProperty("hive.metastore.glue.region", requireNonNull(System.getenv("AWS_REGION"), "AWS_REGION is null")) + .addDeltaProperty("hive.metastore.glue.region", requireEnv("AWS_REGION")) .addDeltaProperty("hive.metastore.glue.default-warehouse-dir", warehouseDir.toUri().toString()) .build(); } @@ -70,7 +70,7 @@ public void testCreateView() { String tableName = "test_glue_table_" + randomNameSuffix(); String viewName = "test_glue_view_" + randomNameSuffix(); - try (TestTable table = new TestTable(getQueryRunner()::execute, tableName, "AS SELECT 'test' x"); + try (TestTable table = newTrinoTable(tableName, "AS SELECT 'test' x"); 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'"); 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 865857a32851..c66ddbb7dbfa 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 @@ -25,7 +25,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static org.assertj.core.api.Assertions.assertThat; public class TestDeltaS3AndGlueMetastoreTest @@ -33,7 +33,7 @@ public class TestDeltaS3AndGlueMetastoreTest { public TestDeltaS3AndGlueMetastoreTest() { - super("partitioned_by", "location", requireNonNull(System.getenv("S3_BUCKET"), "Environment variable not set: S3_BUCKET")); + super("partitioned_by", "location", requireEnv("S3_BUCKET")); } @Override 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 130c788225d6..0960f74ffd23 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 @@ -46,6 +46,7 @@ import static com.google.common.base.Predicates.alwaysTrue; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.filesystem.tracing.FileSystemAttributes.FILE_LOCATION; +import static io.trino.plugin.deltalake.DeltaLakeConfig.DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE; import static io.trino.plugin.deltalake.transactionlog.TableSnapshot.MetadataAndProtocolEntry; import static io.trino.plugin.deltalake.transactionlog.TableSnapshot.load; import static io.trino.plugin.deltalake.transactionlog.TransactionLogParser.readLastCheckpoint; @@ -98,19 +99,23 @@ public void testOnlyReadsTrailingJsonFiles() parquetReaderOptions, true, domainCompactionThreshold, + DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE, Optional.empty())); }, ImmutableMultiset.builder() - .addCopies(new FileOperation("_last_checkpoint", "InputFile.newStream"), 1) - .addCopies(new FileOperation("00000000000000000011.json", "InputFile.newStream"), 1) - .addCopies(new FileOperation("00000000000000000012.json", "InputFile.newStream"), 1) - .addCopies(new FileOperation("00000000000000000013.json", "InputFile.newStream"), 1) - .addCopies(new FileOperation("00000000000000000014.json", "InputFile.newStream"), 1) + .add(new FileOperation("_last_checkpoint", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000012.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000013.json", "InputFile.newStream")) + .add(new FileOperation("00000000000000000011.json", "InputFile.length")) + .add(new FileOperation("00000000000000000012.json", "InputFile.length")) + .add(new FileOperation("00000000000000000013.json", "InputFile.length")) + .add(new FileOperation("00000000000000000014.json", "InputFile.length")) .build()); assertFileSystemAccesses( () -> { - tableSnapshot.get().getJsonTransactionLogEntries().forEach(entry -> {}); + tableSnapshot.get().getJsonTransactionLogEntries(trackingFileSystem).forEach(entry -> {}); }, ImmutableMultiset.of()); } @@ -129,6 +134,7 @@ public void readsCheckpointFile() parquetReaderOptions, true, domainCompactionThreshold, + DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE, Optional.empty()); TestingConnectorContext context = new TestingConnectorContext(); TypeManager typeManager = context.getTypeManager(); @@ -257,6 +263,7 @@ public void testMaxTransactionId() parquetReaderOptions, true, domainCompactionThreshold, + DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE, Optional.empty()); assertThat(tableSnapshot.getVersion()).isEqualTo(13L); } 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 b42a58cb04a7..d4610bd756f5 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 @@ -21,6 +21,7 @@ import java.util.List; import java.util.Optional; +import static io.trino.plugin.deltalake.DeltaLakeConfig.DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE; import static io.trino.plugin.deltalake.DeltaTestingConnectorSession.SESSION; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; @@ -49,17 +50,17 @@ private List updateJsonTransactionLogTails(String throws Exception { 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()); + TransactionLogTail transactionLogTail = TransactionLogTail.loadNewTail(fileSystem, tableLocation, Optional.of(10L), Optional.of(12L), DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE); + Optional updatedLogTail = transactionLogTail.getUpdatedTail(fileSystem, tableLocation, Optional.empty(), DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE); assertThat(updatedLogTail).isPresent(); - return updatedLogTail.get().getFileEntries(); + return updatedLogTail.get().getFileEntries(fileSystem); } private List readJsonTransactionLogTails(String tableLocation) throws Exception { TrinoFileSystem fileSystem = new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS).create(SESSION); - TransactionLogTail transactionLogTail = TransactionLogTail.loadNewTail(fileSystem, tableLocation, Optional.of(10L), Optional.empty()); - return transactionLogTail.getFileEntries(); + TransactionLogTail transactionLogTail = TransactionLogTail.loadNewTail(fileSystem, tableLocation, Optional.of(10L), Optional.empty(), DEFAULT_TRANSACTION_LOG_MAX_CACHED_SIZE); + return transactionLogTail.getFileEntries(fileSystem); } } diff --git a/plugin/trino-delta-lake/src/test/resources/databricks91/region/README.md b/plugin/trino-delta-lake/src/test/resources/databricks91/region/README.md new file mode 100644 index 000000000000..cba2f72da1d1 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks91/region/README.md @@ -0,0 +1,13 @@ +Data generated using Databricks 9.1: + +```sql +CREATE TABLE default.region (regionkey bigint, name string, comment string) +USING DELTA LOCATION 's3://trino-ci-test/default/region'; + +INSERT INTO default.region VALUES +(0, 'AFRICA', 'lar deposits. blithely final packages cajole. regular waters are final requests. regular accounts are according to '), +(1, 'AMERICA', 'hs use ironic, even requests. s'), +(2, 'ASIA', 'ges. thinly even pinto beans ca'), +(3, 'EUROPE', 'ly final courts cajole furiously final excuse'), +(4, 'MIDDLE EAST', 'uickly special accounts cajole carefully blithely close requests. carefully final asymptotes haggle furiousl'); +``` diff --git a/plugin/trino-delta-lake/src/test/resources/databricks91/region/_delta_log/00000000000000000000.json b/plugin/trino-delta-lake/src/test/resources/databricks91/region/_delta_log/00000000000000000000.json new file mode 100644 index 000000000000..d70cd47d56a5 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks91/region/_delta_log/00000000000000000000.json @@ -0,0 +1,3 @@ +{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} +{"metaData":{"id":"2e681e6c-a691-4e01-b6f5-7be057fba57e","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"regionkey\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"name\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"comment\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{},"createdTime":1734911306140}} +{"commitInfo":{"timestamp":1734911306279,"userId":"7853186923043731","userName":"yuya.ebihara@starburstdata.com","operation":"CREATE TABLE","operationParameters":{"isManaged":"false","description":null,"partitionBy":"[]","properties":"{}"},"notebook":{"notebookId":"1841155838656679"},"clusterId":"0705-100855-ka5jat1a","isolationLevel":"SnapshotIsolation","isBlindAppend":true,"operationMetrics":{}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks91/region/_delta_log/00000000000000000001.json b/plugin/trino-delta-lake/src/test/resources/databricks91/region/_delta_log/00000000000000000001.json new file mode 100644 index 000000000000..e6ab37a1df3f --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks91/region/_delta_log/00000000000000000001.json @@ -0,0 +1,3 @@ +{"add":{"path":"part-00000-3a487bbf-c1d3-404f-b99f-d749d1c7419e-c000.snappy.parquet","partitionValues":{},"size":1408,"modificationTime":1734911311000,"dataChange":true,"stats":"{\"numRecords\":2,\"minValues\":{\"regionkey\":0,\"name\":\"AFRICA\",\"comment\":\"hs use ironic, even requests. s\"},\"maxValues\":{\"regionkey\":1,\"name\":\"AMERICA\",\"comment\":\"lar deposits. blithely final pac�\"},\"nullCount\":{\"regionkey\":0,\"name\":0,\"comment\":0}}","tags":{"INSERTION_TIME":"1734911311000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} +{"add":{"path":"part-00001-87059848-17f8-4dc0-a2d6-4f2a6760e3b9-c000.snappy.parquet","partitionValues":{},"size":1446,"modificationTime":1734911311000,"dataChange":true,"stats":"{\"numRecords\":3,\"minValues\":{\"regionkey\":2,\"name\":\"ASIA\",\"comment\":\"ges. thinly even pinto beans ca\"},\"maxValues\":{\"regionkey\":4,\"name\":\"MIDDLE EAST\",\"comment\":\"uickly special accounts cajole c�\"},\"nullCount\":{\"regionkey\":0,\"name\":0,\"comment\":0}}","tags":{"INSERTION_TIME":"1734911311000001","OPTIMIZE_TARGET_SIZE":"268435456"}}} +{"commitInfo":{"timestamp":1734911310660,"userId":"7853186923043731","userName":"yuya.ebihara@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","partitionBy":"[]"},"notebook":{"notebookId":"1841155838656679"},"clusterId":"0705-100855-ka5jat1a","readVersion":0,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"2","numOutputRows":"5","numOutputBytes":"2854"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks91/region/part-00000-3a487bbf-c1d3-404f-b99f-d749d1c7419e-c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks91/region/part-00000-3a487bbf-c1d3-404f-b99f-d749d1c7419e-c000.snappy.parquet new file mode 100644 index 000000000000..1a09077fdf47 Binary files /dev/null and b/plugin/trino-delta-lake/src/test/resources/databricks91/region/part-00000-3a487bbf-c1d3-404f-b99f-d749d1c7419e-c000.snappy.parquet differ diff --git a/plugin/trino-delta-lake/src/test/resources/databricks91/region/part-00001-87059848-17f8-4dc0-a2d6-4f2a6760e3b9-c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks91/region/part-00001-87059848-17f8-4dc0-a2d6-4f2a6760e3b9-c000.snappy.parquet new file mode 100644 index 000000000000..dce5997a2d5d Binary files /dev/null and b/plugin/trino-delta-lake/src/test/resources/databricks91/region/part-00001-87059848-17f8-4dc0-a2d6-4f2a6760e3b9-c000.snappy.parquet differ diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index 90dfae22c2a5..dc3bb142434b 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -198,7 +198,7 @@ org.freemarker freemarker - 2.3.33 + 2.3.34 test diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index e6ff418e58ba..b27ecb50a245 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-example-http/pom.xml b/plugin/trino-example-http/pom.xml index ac0f88877bb0..59e3d0c47ae7 100644 --- a/plugin/trino-example-http/pom.xml +++ b/plugin/trino-example-http/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-example-jdbc/pom.xml b/plugin/trino-example-jdbc/pom.xml index c8f65d9df5e4..2e861e37d6a0 100644 --- a/plugin/trino-example-jdbc/pom.xml +++ b/plugin/trino-example-jdbc/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exasol/pom.xml b/plugin/trino-exasol/pom.xml index ddc7816163f3..f8398dcba998 100644 --- a/plugin/trino-exasol/pom.xml +++ b/plugin/trino-exasol/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index 73b73eb6fd59..b807dad33bb5 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-hdfs/pom.xml b/plugin/trino-exchange-hdfs/pom.xml index 12f0cf3513bc..b42d917a91fa 100644 --- a/plugin/trino-exchange-hdfs/pom.xml +++ b/plugin/trino-exchange-hdfs/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-faker/pom.xml b/plugin/trino-faker/pom.xml index c0d228bead17..c0866b9f3445 100644 --- a/plugin/trino-faker/pom.xml +++ b/plugin/trino-faker/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -40,11 +40,21 @@ configuration + + io.airlift + units + + io.trino trino-main + + io.trino + trino-parser + + io.trino trino-plugin-toolkit @@ -114,12 +124,6 @@ runtime - - io.airlift - units - runtime - - io.trino trino-client @@ -157,6 +161,12 @@ test + + io.trino + trino-testing-services + test + + io.trino trino-tpch diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/ColumnInfo.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/ColumnInfo.java index e2c66de8d1dc..9bd28af238b4 100644 --- a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/ColumnInfo.java +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/ColumnInfo.java @@ -14,6 +14,7 @@ package io.trino.plugin.faker; import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.type.Type; import java.util.Optional; @@ -23,6 +24,10 @@ public record ColumnInfo(FakerColumnHandle handle, ColumnMetadata metadata) { public static final String NULL_PROBABILITY_PROPERTY = "null_probability"; public static final String GENERATOR_PROPERTY = "generator"; + public static final String MIN_PROPERTY = "min"; + public static final String MAX_PROPERTY = "max"; + public static final String ALLOWED_VALUES_PROPERTY = "allowed_values"; + public static final String STEP_PROPERTY = "step"; public ColumnInfo { @@ -35,6 +40,11 @@ public String name() return metadata.getName(); } + public Type type() + { + return metadata.getType(); + } + @Override public String toString() { diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerColumnHandle.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerColumnHandle.java index e6ae47dec501..c7af196596ca 100644 --- a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerColumnHandle.java +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerColumnHandle.java @@ -14,9 +14,38 @@ package io.trino.plugin.faker; +import com.google.common.collect.ImmutableList; +import io.airlift.units.Duration; +import io.trino.spi.TrinoException; import io.trino.spi.connector.ColumnHandle; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.predicate.Domain; +import io.trino.spi.predicate.Range; +import io.trino.spi.predicate.ValueSet; +import io.trino.spi.type.CharType; +import io.trino.spi.type.TimeType; +import io.trino.spi.type.TimeWithTimeZoneType; +import io.trino.spi.type.TimestampType; +import io.trino.spi.type.TimestampWithTimeZoneType; import io.trino.spi.type.Type; +import io.trino.spi.type.VarbinaryType; +import io.trino.spi.type.VarcharType; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.plugin.faker.ColumnInfo.ALLOWED_VALUES_PROPERTY; +import static io.trino.plugin.faker.ColumnInfo.GENERATOR_PROPERTY; +import static io.trino.plugin.faker.ColumnInfo.MAX_PROPERTY; +import static io.trino.plugin.faker.ColumnInfo.MIN_PROPERTY; +import static io.trino.plugin.faker.ColumnInfo.NULL_PROBABILITY_PROPERTY; +import static io.trino.plugin.faker.ColumnInfo.STEP_PROPERTY; +import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_PROPERTY; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.DateType.DATE; import static java.util.Objects.requireNonNull; public record FakerColumnHandle( @@ -24,12 +53,125 @@ public record FakerColumnHandle( String name, Type type, double nullProbability, - String generator) + String generator, + Domain domain, + ValueSet step) implements ColumnHandle { public FakerColumnHandle { requireNonNull(name, "name is null"); requireNonNull(type, "type is null"); + requireNonNull(domain, "domain is null"); + requireNonNull(step, "step is null"); + checkState(step.isNone() || step.isSingleValue(), "step must be a single value"); + } + + public static FakerColumnHandle of(int columnId, ColumnMetadata column, double defaultNullProbability) + { + double nullProbability = 0; + if (column.isNullable()) { + nullProbability = (double) column.getProperties().getOrDefault(NULL_PROBABILITY_PROPERTY, defaultNullProbability); + } + String generator = (String) column.getProperties().get(GENERATOR_PROPERTY); + if (generator != null && !isCharacterColumn(column)) { + throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `%s` property can only be set for CHAR, VARCHAR or VARBINARY columns".formatted(GENERATOR_PROPERTY)); + } + Object min = propertyValue(column, MIN_PROPERTY); + Object max = propertyValue(column, MAX_PROPERTY); + Domain domain = Domain.all(column.getType()); + if (min != null || max != null) { + if (isCharacterColumn(column)) { + throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `%s` and `%s` properties cannot be set for CHAR, VARCHAR or VARBINARY columns".formatted(MIN_PROPERTY, MAX_PROPERTY)); + } + domain = Domain.create(ValueSet.ofRanges(range(column.getType(), min, max)), false); + } + if (column.getProperties().containsKey(ALLOWED_VALUES_PROPERTY)) { + if (min != null || max != null || generator != null) { + throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `%s` property cannot be set together with `%s`, `%s`, and `%s` properties".formatted(ALLOWED_VALUES_PROPERTY, MIN_PROPERTY, MAX_PROPERTY, GENERATOR_PROPERTY)); + } + ImmutableList.Builder builder = ImmutableList.builder(); + for (String value : strings((List) column.getProperties().get(ALLOWED_VALUES_PROPERTY))) { + try { + builder.add(Literal.parse(value, column.getType())); + } + catch (IllegalArgumentException | ClassCastException e) { + throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `%s` property must only contain valid %s literals, failed to parse `%s`".formatted(ALLOWED_VALUES_PROPERTY, column.getType().getDisplayName(), value), e); + } + } + domain = Domain.create(ValueSet.copyOf(column.getType(), builder.build()), false); + } + + return new FakerColumnHandle( + columnId, + column.getName(), + column.getType(), + nullProbability, + generator, + domain, + stepValue(column)); + } + + private static boolean isCharacterColumn(ColumnMetadata column) + { + return column.getType() instanceof CharType || column.getType() instanceof VarcharType || column.getType() instanceof VarbinaryType; + } + + private static Object propertyValue(ColumnMetadata column, String property) + { + try { + return Literal.parse((String) column.getProperties().get(property), column.getType()); + } + catch (IllegalArgumentException e) { + throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `%s` property must be a valid %s literal".formatted(property, column.getType().getDisplayName()), e); + } + } + + private static ValueSet stepValue(ColumnMetadata column) + { + Type type = column.getType(); + String rawStep = (String) column.getProperties().get(STEP_PROPERTY); + if (rawStep == null) { + return ValueSet.none(type); + } + if (isCharacterColumn(column)) { + throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `%s` property cannot be set for CHAR, VARCHAR or VARBINARY columns".formatted(STEP_PROPERTY)); + } + if (DATE.equals(column.getType()) || type instanceof TimestampType || type instanceof TimestampWithTimeZoneType || type instanceof TimeType || type instanceof TimeWithTimeZoneType) { + try { + return ValueSet.of(BIGINT, Duration.valueOf(rawStep).roundTo(TimeUnit.NANOSECONDS)); + } + catch (IllegalArgumentException e) { + throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `%s` property for a %s column must be a valid duration literal".formatted(STEP_PROPERTY, column.getType().getDisplayName()), e); + } + } + try { + return ValueSet.of(type, Literal.parse(rawStep, type)); + } + catch (IllegalArgumentException e) { + throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `%s` property for a %s column must be a valid %s literal".formatted(STEP_PROPERTY, column.getType().getDisplayName(), type.getDisplayName()), e); + } + } + + private static Range range(Type type, Object min, Object max) + { + requireNonNull(type, "type is null"); + if (min == null && max == null) { + return Range.all(type); + } + if (max == null) { + return Range.greaterThanOrEqual(type, min); + } + if (min == null) { + return Range.lessThanOrEqual(type, max); + } + return Range.range(type, min, true, max, true); + } + + private static List strings(Collection values) + { + return values.stream() + .map(String.class::cast) + .collect(toImmutableList()); } } diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerConnector.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerConnector.java index 0d29c986f965..734fa513b355 100644 --- a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerConnector.java +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerConnector.java @@ -28,12 +28,18 @@ import io.trino.spi.function.FunctionProvider; import io.trino.spi.session.PropertyMetadata; import io.trino.spi.transaction.IsolationLevel; +import io.trino.spi.type.ArrayType; import jakarta.inject.Inject; import java.util.List; import java.util.Optional; import java.util.Set; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.plugin.faker.ColumnInfo.ALLOWED_VALUES_PROPERTY; +import static io.trino.plugin.faker.ColumnInfo.MAX_PROPERTY; +import static io.trino.plugin.faker.ColumnInfo.MIN_PROPERTY; +import static io.trino.plugin.faker.ColumnInfo.STEP_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; @@ -41,6 +47,7 @@ import static io.trino.spi.session.PropertyMetadata.doubleProperty; import static io.trino.spi.session.PropertyMetadata.longProperty; import static io.trino.spi.session.PropertyMetadata.stringProperty; +import static io.trino.spi.type.VarcharType.VARCHAR; import static java.util.Objects.requireNonNull; public class FakerConnector @@ -161,6 +168,32 @@ public List> getColumnProperties() throw new TrinoException(INVALID_COLUMN_PROPERTY, "generator must be a valid Faker expression", e); } }, + false), + stringProperty( + MIN_PROPERTY, + "Minimum generated value (inclusive)", + null, + false), + stringProperty( + MAX_PROPERTY, + "Maximum generated value (inclusive)", + null, + false), + new PropertyMetadata<>( + ALLOWED_VALUES_PROPERTY, + "List of allowed values", + new ArrayType(VARCHAR), + List.class, + null, + false, + value -> ((List) value).stream() + .map(String.class::cast) + .collect(toImmutableList()), + value -> value), + stringProperty( + STEP_PROPERTY, + "If set, generate sequential values with this step. For date and time columns set this to a duration", + null, false)); } diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerMetadata.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerMetadata.java index 0e3be1ef747f..46d246e9d8d0 100644 --- a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerMetadata.java +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerMetadata.java @@ -15,6 +15,7 @@ package io.trino.plugin.faker; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.concurrent.GuardedBy; import io.airlift.slice.Slice; import io.trino.spi.TrinoException; @@ -28,28 +29,25 @@ import io.trino.spi.connector.ConnectorTableLayout; import io.trino.spi.connector.ConnectorTableMetadata; import io.trino.spi.connector.ConnectorTableVersion; -import io.trino.spi.connector.Constraint; -import io.trino.spi.connector.ConstraintApplicationResult; +import io.trino.spi.connector.ConnectorViewDefinition; import io.trino.spi.connector.LimitApplicationResult; +import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RetryMode; import io.trino.spi.connector.SaveMode; -import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.SchemaTablePrefix; -import io.trino.spi.connector.TableColumnsMetadata; +import io.trino.spi.connector.ViewNotFoundException; import io.trino.spi.function.BoundSignature; import io.trino.spi.function.FunctionDependencyDeclaration; import io.trino.spi.function.FunctionId; import io.trino.spi.function.FunctionMetadata; import io.trino.spi.function.SchemaFunctionName; import io.trino.spi.predicate.Domain; -import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.predicate.Range; +import io.trino.spi.predicate.ValueSet; import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.statistics.ComputedStatistics; import io.trino.spi.type.BigintType; -import io.trino.spi.type.CharType; -import io.trino.spi.type.VarbinaryType; -import io.trino.spi.type.VarcharType; import jakarta.inject.Inject; import java.util.ArrayList; @@ -59,14 +57,19 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; +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.spi.StandardErrorCode.INVALID_COLUMN_PROPERTY; +import static com.google.common.collect.Maps.filterKeys; +import static io.trino.spi.StandardErrorCode.ALREADY_EXISTS; import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_REFERENCE; +import static io.trino.spi.StandardErrorCode.NOT_FOUND; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.StandardErrorCode.SCHEMA_ALREADY_EXISTS; import static io.trino.spi.StandardErrorCode.SCHEMA_NOT_FOUND; @@ -85,11 +88,12 @@ public class FakerMetadata private final List schemas = new ArrayList<>(); private final double nullProbability; private final long defaultLimit; + private final FakerFunctionProvider functionsProvider; @GuardedBy("this") private final Map tables = new HashMap<>(); - - private final FakerFunctionProvider functionsProvider; + @GuardedBy("this") + private final Map views = new HashMap<>(); @Inject public FakerMetadata(FakerConfig config, FakerFunctionProvider functionProvider) @@ -147,7 +151,7 @@ public synchronized ConnectorTableHandle getTableHandle(ConnectorSession session } long schemaLimit = (long) schema.properties().getOrDefault(SchemaInfo.DEFAULT_LIMIT_PROPERTY, defaultLimit); long tableLimit = (long) tables.get(tableName).properties().getOrDefault(TableInfo.DEFAULT_LIMIT_PROPERTY, schemaLimit); - return new FakerTableHandle(tableName, TupleDomain.all(), tableLimit); + return new FakerTableHandle(tableName, tableLimit); } @Override @@ -168,7 +172,7 @@ public synchronized ConnectorTableMetadata getTableMetadata( @Override public synchronized List listTables(ConnectorSession session, Optional schemaName) { - return tables.keySet().stream() + return Stream.concat(tables.keySet().stream(), views.keySet().stream()) .filter(table -> schemaName.map(table.getSchemaName()::contentEquals).orElse(true)) .collect(toImmutableList()); } @@ -197,13 +201,33 @@ public synchronized ColumnMetadata getColumnMetadata( } @Override - public synchronized Iterator streamTableColumns(ConnectorSession session, SchemaTablePrefix prefix) + public synchronized Iterator streamRelationColumns( + ConnectorSession session, + Optional schemaName, + UnaryOperator> relationFilter) { - return tables.entrySet().stream() + Map relationColumns = new HashMap<>(); + + SchemaTablePrefix prefix = schemaName.map(SchemaTablePrefix::new) + .orElseGet(SchemaTablePrefix::new); + tables.entrySet().stream() .filter(entry -> prefix.matches(entry.getKey())) - .map(entry -> TableColumnsMetadata.forTable( - entry.getKey(), - entry.getValue().columns().stream().map(ColumnInfo::metadata).collect(toImmutableList()))) + .forEach(entry -> { + SchemaTableName name = entry.getKey(); + RelationColumnsMetadata columns = RelationColumnsMetadata.forTable( + name, + entry.getValue().columns().stream() + .map(ColumnInfo::metadata) + .collect(toImmutableList())); + relationColumns.put(name, columns); + }); + + for (Map.Entry entry : getViews(session, schemaName).entrySet()) { + relationColumns.put(entry.getKey(), RelationColumnsMetadata.forView(entry.getKey(), entry.getValue().getColumns())); + } + + return relationFilter.apply(relationColumns.keySet()).stream() + .map(relationColumns::get) .iterator(); } @@ -219,12 +243,12 @@ public synchronized void renameTable(ConnectorSession session, ConnectorTableHan { checkSchemaExists(newTableName.getSchemaName()); checkTableNotExists(newTableName); + checkViewNotExists(newTableName); FakerTableHandle handle = (FakerTableHandle) tableHandle; SchemaTableName oldTableName = handle.schemaTableName(); - tables.remove(oldTableName); - tables.put(newTableName, tables.get(oldTableName)); + tables.put(newTableName, tables.remove(oldTableName)); } @Override @@ -293,6 +317,7 @@ public synchronized FakerOutputTableHandle beginCreateTable(ConnectorSession ses SchemaTableName tableName = tableMetadata.getTable(); SchemaInfo schema = getSchema(tableName.getSchemaName()); checkTableNotExists(tableMetadata.getTable()); + checkViewNotExists(tableMetadata.getTable()); double schemaNullProbability = (double) schema.properties().getOrDefault(SchemaInfo.NULL_PROBABILITY_PROPERTY, nullProbability); double tableNullProbability = (double) tableMetadata.getProperties().getOrDefault(TableInfo.NULL_PROBABILITY_PROPERTY, schemaNullProbability); @@ -301,21 +326,8 @@ public synchronized FakerOutputTableHandle beginCreateTable(ConnectorSession ses int columnId = 0; for (; columnId < tableMetadata.getColumns().size(); columnId++) { ColumnMetadata column = tableMetadata.getColumns().get(columnId); - double nullProbability = 0; - if (column.isNullable()) { - nullProbability = (double) column.getProperties().getOrDefault(ColumnInfo.NULL_PROBABILITY_PROPERTY, tableNullProbability); - } - String generator = (String) column.getProperties().get(ColumnInfo.GENERATOR_PROPERTY); - if (generator != null && !isCharacterColumn(column)) { - throw new TrinoException(INVALID_COLUMN_PROPERTY, "The `generator` property can only be set for CHAR, VARCHAR or VARBINARY columns"); - } columns.add(new ColumnInfo( - new FakerColumnHandle( - columnId, - column.getName(), - column.getType(), - nullProbability, - generator), + FakerColumnHandle.of(columnId, column, tableNullProbability), column)); } @@ -325,7 +337,9 @@ public synchronized FakerOutputTableHandle beginCreateTable(ConnectorSession ses ROW_ID_COLUMN_NAME, BigintType.BIGINT, 0, - ""), + "", + Domain.create(ValueSet.ofRanges(Range.greaterThanOrEqual(BigintType.BIGINT, 0L)), false), + ValueSet.of(BigintType.BIGINT, 1L)), ColumnMetadata.builder() .setName(ROW_ID_COLUMN_NAME) .setType(BigintType.BIGINT) @@ -341,15 +355,10 @@ public synchronized FakerOutputTableHandle beginCreateTable(ConnectorSession ses return new FakerOutputTableHandle(tableName); } - private boolean isCharacterColumn(ColumnMetadata column) - { - return column.getType() instanceof CharType || column.getType() instanceof VarcharType || column.getType() instanceof VarbinaryType; - } - private synchronized void checkSchemaExists(String schemaName) { if (schemas.stream().noneMatch(schema -> schema.name().equals(schemaName))) { - throw new SchemaNotFoundException(schemaName); + throw new TrinoException(SCHEMA_NOT_FOUND, format("Schema '%s' does not exist", schemaName)); } } @@ -360,8 +369,19 @@ private synchronized void checkTableNotExists(SchemaTableName tableName) } } + private synchronized void checkViewNotExists(SchemaTableName viewName) + { + if (views.containsKey(viewName)) { + throw new TrinoException(ALREADY_EXISTS, format("View '%s' already exists", viewName)); + } + } + @Override - public synchronized Optional finishCreateTable(ConnectorSession session, ConnectorOutputTableHandle tableHandle, Collection fragments, Collection computedStatistics) + public synchronized Optional finishCreateTable( + ConnectorSession session, + ConnectorOutputTableHandle tableHandle, + Collection fragments, + Collection computedStatistics) { requireNonNull(tableHandle, "tableHandle is null"); FakerOutputTableHandle fakerOutputHandle = (FakerOutputTableHandle) tableHandle; @@ -371,12 +391,108 @@ public synchronized Optional finishCreateTable(Connecto TableInfo info = tables.get(tableName); requireNonNull(info, "info is null"); - // TODO ensure fragments is empty? - tables.put(tableName, new TableInfo(info.columns(), info.properties(), info.comment())); + return Optional.empty(); } + @Override + public synchronized List listViews(ConnectorSession session, Optional schemaName) + { + return views.keySet().stream() + .filter(table -> schemaName.map(table.getSchemaName()::equals).orElse(true)) + .collect(toImmutableList()); + } + + @Override + public synchronized Map getViews(ConnectorSession session, Optional schemaName) + { + SchemaTablePrefix prefix = schemaName.map(SchemaTablePrefix::new).orElseGet(SchemaTablePrefix::new); + return ImmutableMap.copyOf(filterKeys(views, prefix::matches)); + } + + @Override + public synchronized Optional getView(ConnectorSession session, SchemaTableName viewName) + { + return Optional.ofNullable(views.get(viewName)); + } + + @Override + public synchronized void createView( + ConnectorSession session, + SchemaTableName viewName, + ConnectorViewDefinition definition, + Map viewProperties, + boolean replace) + { + checkArgument(viewProperties.isEmpty(), "This connector does not support creating views with properties"); + checkSchemaExists(viewName.getSchemaName()); + checkTableNotExists(viewName); + + if (replace) { + views.put(viewName, definition); + } + else if (views.putIfAbsent(viewName, definition) != null) { + throw new TrinoException(ALREADY_EXISTS, "View '%s' already exists".formatted(viewName)); + } + } + + @Override + public synchronized void setViewComment(ConnectorSession session, SchemaTableName viewName, Optional comment) + { + ConnectorViewDefinition view = getView(session, viewName).orElseThrow(() -> new ViewNotFoundException(viewName)); + views.put(viewName, new ConnectorViewDefinition( + view.getOriginalSql(), + view.getCatalog(), + view.getSchema(), + view.getColumns(), + comment, + view.getOwner(), + view.isRunAsInvoker(), + view.getPath())); + } + + @Override + public synchronized void setViewColumnComment(ConnectorSession session, SchemaTableName viewName, String columnName, Optional comment) + { + ConnectorViewDefinition view = getView(session, viewName).orElseThrow(() -> new ViewNotFoundException(viewName)); + views.put(viewName, new ConnectorViewDefinition( + view.getOriginalSql(), + view.getCatalog(), + view.getSchema(), + view.getColumns().stream() + .map(currentViewColumn -> columnName.equals(currentViewColumn.getName()) ? + new ConnectorViewDefinition.ViewColumn(currentViewColumn.getName(), currentViewColumn.getType(), comment) + : currentViewColumn) + .collect(toImmutableList()), + view.getComment(), + view.getOwner(), + view.isRunAsInvoker(), + view.getPath())); + } + + @Override + public synchronized void renameView(ConnectorSession session, SchemaTableName source, SchemaTableName target) + { + checkSchemaExists(target.getSchemaName()); + if (!views.containsKey(source)) { + throw new TrinoException(NOT_FOUND, "View not found: " + source); + } + checkTableNotExists(target); + + if (views.putIfAbsent(target, views.remove(source)) != null) { + throw new TrinoException(ALREADY_EXISTS, "View '%s' already exists".formatted(target)); + } + } + + @Override + public synchronized void dropView(ConnectorSession session, SchemaTableName viewName) + { + if (views.remove(viewName) == null) { + throw new ViewNotFoundException(viewName); + } + } + @Override public synchronized Map getSchemaProperties(ConnectorSession session, String schemaName) { @@ -406,62 +522,6 @@ public Optional> applyLimit( true)); } - @Override - public Optional> applyFilter( - ConnectorSession session, - ConnectorTableHandle table, - Constraint constraint) - { - FakerTableHandle fakerTable = (FakerTableHandle) table; - - TupleDomain summary = constraint.getSummary(); - if (summary.isAll()) { - return Optional.empty(); - } - // the only reason not to use isNone is so the linter doesn't complain about not checking an Optional - if (summary.getDomains().isEmpty()) { - throw new IllegalArgumentException("summary cannot be none"); - } - - TupleDomain currentConstraint = fakerTable.constraint(); - if (currentConstraint.getDomains().isEmpty()) { - throw new IllegalArgumentException("currentConstraint is none but should be all!"); - } - - // push down everything, unsupported constraints will throw an exception during data generation - boolean anyUpdated = false; - for (Map.Entry entry : summary.getDomains().get().entrySet()) { - FakerColumnHandle column = (FakerColumnHandle) entry.getKey(); - Domain domain = entry.getValue(); - - if (currentConstraint.getDomains().get().containsKey(column)) { - Domain currentDomain = currentConstraint.getDomains().get().get(column); - // it is important to avoid processing same constraint multiple times - // so that planner doesn't get stuck in a loop - if (currentDomain.equals(domain)) { - continue; - } - // TODO write test cases for this, it doesn't seem to work with IS NULL - currentDomain.union(domain); - } - else { - Map domains = new HashMap<>(currentConstraint.getDomains().get()); - domains.put(column, domain); - currentConstraint = TupleDomain.withColumnDomains(domains); - } - anyUpdated = true; - } - if (!anyUpdated) { - return Optional.empty(); - } - - return Optional.of(new ConstraintApplicationResult<>( - fakerTable.withConstraint(currentConstraint), - TupleDomain.all(), - constraint.getExpression(), - true)); - } - @Override public Collection listFunctions(ConnectorSession session, String schemaName) { diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSource.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSource.java index cf8ac1310c1f..e1df2900bccb 100644 --- a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSource.java +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSource.java @@ -19,11 +19,8 @@ import io.trino.spi.PageBuilder; import io.trino.spi.TrinoException; import io.trino.spi.block.BlockBuilder; -import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ConnectorPageSource; -import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.Range; -import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.CharType; import io.trino.spi.type.DecimalType; import io.trino.spi.type.Decimals; @@ -44,6 +41,7 @@ import io.trino.type.IpAddressType; import net.datafaker.Faker; +import java.math.BigDecimal; import java.math.BigInteger; import java.net.Inet4Address; import java.net.UnknownHostException; @@ -52,7 +50,6 @@ import java.util.Random; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.faker.FakerMetadata.ROW_ID_COLUMN_NAME; import static io.trino.spi.StandardErrorCode.INVALID_ROW_FILTER; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; @@ -69,9 +66,12 @@ import static io.trino.spi.type.RealType.REAL; import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_DAY; +import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_MICROSECOND; +import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_MILLISECOND; import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_DAY; import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_MICROSECOND; import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_MILLISECOND; +import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND; import static io.trino.spi.type.Timestamps.roundDiv; import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.UuidType.UUID; @@ -114,6 +114,8 @@ class FakerPageSource private final Random random; private final Faker faker; + private final SentenceGenerator sentenceGenerator; + private final BoundedSentenceGenerator boundedSentenceGenerator; private final long limit; private final List generators; private long completedRows; @@ -126,45 +128,53 @@ class FakerPageSource Faker faker, Random random, List columns, - TupleDomain constraint, - long offset, + long rowOffset, long limit) { this.faker = requireNonNull(faker, "faker is null"); this.random = requireNonNull(random, "random is null"); + this.sentenceGenerator = () -> Slices.utf8Slice(faker.lorem().sentence(3 + random.nextInt(38))); + this.boundedSentenceGenerator = (maxLength) -> Slices.utf8Slice(faker.lorem().maxLengthSentence(maxLength)); List types = requireNonNull(columns, "columns is null") .stream() .map(FakerColumnHandle::type) .collect(toImmutableList()); - requireNonNull(constraint, "constraint is null"); this.limit = limit; this.generators = columns .stream() - .map(column -> getGenerator(column, constraint, offset)) + .map(column -> getGenerator(column, rowOffset)) .collect(toImmutableList()); this.pageBuilder = new PageBuilder(types); } private Generator getGenerator( FakerColumnHandle column, - TupleDomain constraint, - long offset) - { - if (ROW_ID_COLUMN_NAME.equals(column.name())) { - return new Generator() { - long currentRowId = offset; - @Override - public void accept(BlockBuilder blockBuilder) - { - BIGINT.writeLong(blockBuilder, currentRowId++); - } - }; + long rowOffset) + { + if (column.domain().getValues().isDiscreteSet()) { + List values = column.domain().getValues().getDiscreteSet(); + ObjectWriter singleValueWriter = objectWriter(column.type()); + return (blockBuilder) -> singleValueWriter.accept(blockBuilder, values.get(random.nextInt(values.size()))); } - - return constraintedValueGenerator( - column, - constraint.getDomains().get().getOrDefault(column, Domain.all(column.type()))); + Generator generator; + if (!column.step().isNone()) { + generator = sequenceGenerator(column, rowOffset); + } + else { + generator = randomValueGenerator(column); + } + if (column.nullProbability() == 0) { + return generator; + } + return (blockBuilder) -> { + if (random.nextDouble() <= column.nullProbability()) { + blockBuilder.appendNull(); + } + else { + generator.accept(blockBuilder); + } + }; } @Override @@ -228,39 +238,108 @@ public void close() closed = true; } - private Generator constraintedValueGenerator(FakerColumnHandle handle, Domain domain) + private Generator sequenceGenerator(FakerColumnHandle handle, long rowOffset) + { + SequenceWriter writer = sequenceWriter(handle); + + return new Generator() + { + long currentRowId = rowOffset; + + @Override + public void accept(BlockBuilder blockBuilder) + { + writer.accept(blockBuilder, currentRowId++); + } + }; + } + + private SequenceWriter sequenceWriter(FakerColumnHandle handle) { - if (domain.isSingleValue()) { - ObjectWriter singleValueWriter = objectWriter(handle.type()); - return (blockBuilder) -> singleValueWriter.accept(blockBuilder, domain.getSingleValue()); + Range genericRange = handle.domain().getValues().getRanges().getSpan(); + Type type = handle.type(); + // check every type in order defined in StandardTypes + // not supported: BOOLEAN, HYPER_LOG_LOG, QDIGEST, TDIGEST, P4_HYPER_LOG_LOG, VARBINARY, VARCHAR, CHAR, ROW, ARRAY, MAP, JSON, IPADDRESS, GEOMETRY, UUID + if (BIGINT.equals(type)) { + LongRange range = LongRange.of(genericRange, 1, (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> BIGINT.writeLong(blockBuilder, range.at(rowId)); } - if (domain.getValues().isDiscreteSet()) { - List values = domain.getValues().getDiscreteSet(); - ObjectWriter singleValueWriter = objectWriter(handle.type()); - return (blockBuilder) -> singleValueWriter.accept(blockBuilder, values.get(random.nextInt(values.size()))); + if (INTEGER.equals(type)) { + IntRange range = IntRange.of(genericRange, (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> INTEGER.writeLong(blockBuilder, range.at(rowId)); } - if (domain.getValues().getRanges().getRangeCount() > 1) { - // this would require calculating weights for each range to retain uniform distribution - throw new TrinoException(INVALID_ROW_FILTER, "Generating random values from more than one range is not supported"); + if (SMALLINT.equals(type)) { + IntRange range = IntRange.of(genericRange, Short.MIN_VALUE, Short.MAX_VALUE, (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> SMALLINT.writeLong(blockBuilder, range.at(rowId)); } - Generator generator = randomValueGenerator(handle, domain.getValues().getRanges().getSpan()); - if (handle.nullProbability() == 0) { - return generator; + if (TINYINT.equals(type)) { + IntRange range = IntRange.of(genericRange, Byte.MIN_VALUE, Byte.MAX_VALUE, (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> TINYINT.writeLong(blockBuilder, range.at(rowId)); } - return (blockBuilder) -> { - if (random.nextDouble() <= handle.nullProbability()) { - blockBuilder.appendNull(); + if (DATE.equals(type)) { + IntRange range = IntRange.of(genericRange, (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> DATE.writeLong(blockBuilder, range.at(rowId, NANOSECONDS_PER_DAY)); + } + if (type instanceof DecimalType decimalType) { + if (decimalType.isShort()) { + ShortDecimalRange range = ShortDecimalRange.of(genericRange, decimalType.getPrecision(), (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> decimalType.writeLong(blockBuilder, range.at(rowId)); } - else { - generator.accept(blockBuilder); + Int128Range range = Int128Range.of(genericRange, (Int128) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> decimalType.writeObject(blockBuilder, range.at(rowId)); + } + if (REAL.equals(type)) { + FloatRange range = FloatRange.of(genericRange, intBitsToFloat(toIntExact((long) handle.step().getSingleValue()))); + return (blockBuilder, rowId) -> REAL.writeLong(blockBuilder, floatToRawIntBits(range.at(rowId))); + } + if (DOUBLE.equals(type)) { + DoubleRange range = DoubleRange.of(genericRange, (double) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> DOUBLE.writeDouble(blockBuilder, range.at(rowId)); + } + if (INTERVAL_DAY_TIME.equals(type) || INTERVAL_YEAR_MONTH.equals(type)) { + // step is seconds or months + IntRange range = IntRange.of(genericRange, (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> type.writeLong(blockBuilder, range.at(rowId)); + } + if (type instanceof TimestampType timestampType) { + if (timestampType.isShort()) { + long factor = POWERS_OF_TEN[6 - timestampType.getPrecision()]; + LongRange range = LongRange.of(genericRange, factor, (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> timestampType.writeLong(blockBuilder, range.at(rowId, factor * NANOSECONDS_PER_MICROSECOND) * factor); } - }; + LongTimestampRange range = LongTimestampRange.of(genericRange, timestampType.getPrecision(), (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> timestampType.writeObject(blockBuilder, range.at(rowId, NANOSECONDS_PER_MICROSECOND)); + } + if (type instanceof TimestampWithTimeZoneType tzType) { + if (tzType.isShort()) { + ShortTimestampWithTimeZoneRange range = ShortTimestampWithTimeZoneRange.of(genericRange, tzType.getPrecision(), (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> tzType.writeLong(blockBuilder, range.at(rowId, NANOSECONDS_PER_MILLISECOND)); + } + LongTimestampWithTimeZoneRange range = LongTimestampWithTimeZoneRange.of(genericRange, tzType.getPrecision(), (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> tzType.writeObject(blockBuilder, range.at(rowId, NANOSECONDS_PER_MILLISECOND)); + } + if (type instanceof TimeType timeType) { + long factor = POWERS_OF_TEN[12 - timeType.getPrecision()]; + LongRange range = LongRange.of(genericRange, factor, 0, PICOSECONDS_PER_DAY, (long) handle.step().getSingleValue() * PICOSECONDS_PER_NANOSECOND); + return (blockBuilder, rowId) -> timeType.writeLong(blockBuilder, range.at(rowId, factor) * factor); + } + if (type instanceof TimeWithTimeZoneType timeType) { + if (timeType.isShort()) { + ShortTimeWithTimeZoneRange range = ShortTimeWithTimeZoneRange.of(genericRange, timeType.getPrecision(), (long) handle.step().getSingleValue()); + return (blockBuilder, rowId) -> timeType.writeLong(blockBuilder, range.at(rowId)); + } + LongTimeWithTimeZoneRange range = LongTimeWithTimeZoneRange.of(genericRange, timeType.getPrecision(), (long) handle.step().getSingleValue() * PICOSECONDS_PER_NANOSECOND); + return (blockBuilder, rowId) -> timeType.writeObject(blockBuilder, range.at(rowId)); + } + + throw new IllegalArgumentException("Unsupported type " + type); } - private Generator randomValueGenerator(FakerColumnHandle handle, Range range) + private Generator randomValueGenerator(FakerColumnHandle handle) { + Range genericRange = handle.domain().getValues().getRanges().getSpan(); if (handle.generator() != null) { - if (!range.isAll()) { + if (!genericRange.isAll()) { throw new TrinoException(INVALID_ROW_FILTER, "Predicates for columns with a generator expression are not supported"); } return (blockBuilder) -> VARCHAR.writeSlice(blockBuilder, Slices.utf8Slice(faker.expression(handle.generator()))); @@ -268,84 +347,94 @@ private Generator randomValueGenerator(FakerColumnHandle handle, Range range) Type type = handle.type(); // check every type in order defined in StandardTypes if (BIGINT.equals(type)) { - return (blockBuilder) -> BIGINT.writeLong(blockBuilder, generateLong(range, 1)); + LongRange range = LongRange.of(genericRange); + return (blockBuilder) -> BIGINT.writeLong(blockBuilder, numberBetween(range.low, range.high)); } if (INTEGER.equals(type)) { - return (blockBuilder) -> INTEGER.writeLong(blockBuilder, generateInt(range)); + IntRange range = IntRange.of(genericRange); + return (blockBuilder) -> INTEGER.writeLong(blockBuilder, numberBetween(range.low, range.high)); } if (SMALLINT.equals(type)) { - return (blockBuilder) -> SMALLINT.writeLong(blockBuilder, generateShort(range)); + IntRange range = IntRange.of(genericRange, Short.MIN_VALUE, Short.MAX_VALUE); + return (blockBuilder) -> SMALLINT.writeLong(blockBuilder, numberBetween(range.low, range.high)); } if (TINYINT.equals(type)) { - return (blockBuilder) -> TINYINT.writeLong(blockBuilder, generateTiny(range)); + IntRange range = IntRange.of(genericRange, Byte.MIN_VALUE, Byte.MAX_VALUE); + return (blockBuilder) -> TINYINT.writeLong(blockBuilder, numberBetween(range.low, range.high)); } if (BOOLEAN.equals(type)) { - if (!range.isAll()) { + if (!genericRange.isAll()) { throw new TrinoException(INVALID_ROW_FILTER, "Range or not a single value predicates for boolean columns are not supported"); } return (blockBuilder) -> BOOLEAN.writeBoolean(blockBuilder, random.nextBoolean()); } if (DATE.equals(type)) { - return (blockBuilder) -> DATE.writeLong(blockBuilder, generateInt(range)); + IntRange range = IntRange.of(genericRange); + return (blockBuilder) -> DATE.writeLong(blockBuilder, numberBetween(range.low, range.high)); } if (type instanceof DecimalType decimalType) { - return decimalGenerator(range, decimalType); + return decimalGenerator(genericRange, decimalType); } if (REAL.equals(type)) { - return (blockBuilder) -> REAL.writeLong(blockBuilder, floatToRawIntBits(generateFloat(range))); + FloatRange range = FloatRange.of(genericRange); + return (blockBuilder) -> REAL.writeLong(blockBuilder, floatToRawIntBits(range.low == range.high ? range.low : random.nextFloat(range.low, range.high))); } if (DOUBLE.equals(type)) { - return (blockBuilder) -> DOUBLE.writeDouble(blockBuilder, generateDouble(range)); + DoubleRange range = DoubleRange.of(genericRange); + return (blockBuilder) -> DOUBLE.writeDouble(blockBuilder, range.low == range.high ? range.low : random.nextDouble(range.low, range.high)); } // not supported: HYPER_LOG_LOG, QDIGEST, TDIGEST, P4_HYPER_LOG_LOG if (INTERVAL_DAY_TIME.equals(type)) { - return (blockBuilder) -> INTERVAL_DAY_TIME.writeLong(blockBuilder, generateLong(range, 1)); + LongRange range = LongRange.of(genericRange); + return (blockBuilder) -> INTERVAL_DAY_TIME.writeLong(blockBuilder, numberBetween(range.low, range.high)); } if (INTERVAL_YEAR_MONTH.equals(type)) { - return (blockBuilder) -> INTERVAL_YEAR_MONTH.writeLong(blockBuilder, generateInt(range)); + IntRange range = IntRange.of(genericRange); + return (blockBuilder) -> INTERVAL_YEAR_MONTH.writeLong(blockBuilder, numberBetween(range.low, range.high)); } - if (type instanceof TimestampType) { - return timestampGenerator(range, (TimestampType) type); + if (type instanceof TimestampType timestampType) { + return timestampGenerator(genericRange, timestampType); } - if (type instanceof TimestampWithTimeZoneType) { - return timestampWithTimeZoneGenerator(range, (TimestampWithTimeZoneType) type); + if (type instanceof TimestampWithTimeZoneType timestampWithTimeZoneType) { + return timestampWithTimeZoneGenerator(genericRange, timestampWithTimeZoneType); } if (type instanceof TimeType timeType) { long factor = POWERS_OF_TEN[12 - timeType.getPrecision()]; - return (blockBuilder) -> timeType.writeLong(blockBuilder, generateLongDefaults(range, factor, 0, PICOSECONDS_PER_DAY)); + LongRange range = LongRange.of(genericRange, factor, 0, PICOSECONDS_PER_DAY); + return (blockBuilder) -> timeType.writeLong(blockBuilder, numberBetween(range.low, range.high) * factor); } - if (type instanceof TimeWithTimeZoneType) { - return timeWithTimeZoneGenerator(range, (TimeWithTimeZoneType) type); + if (type instanceof TimeWithTimeZoneType timeWithTimeZoneType) { + return timeWithTimeZoneGenerator(genericRange, timeWithTimeZoneType); } if (type instanceof VarbinaryType varType) { - if (!range.isAll()) { + if (!genericRange.isAll()) { throw new TrinoException(INVALID_ROW_FILTER, "Predicates for varbinary columns are not supported"); } - return (blockBuilder) -> varType.writeSlice(blockBuilder, Slices.utf8Slice(faker.lorem().sentence(3 + random.nextInt(38)))); + return (blockBuilder) -> varType.writeSlice(blockBuilder, sentenceGenerator.get()); } if (type instanceof VarcharType varcharType) { - if (!range.isAll()) { + if (!genericRange.isAll()) { throw new TrinoException(INVALID_ROW_FILTER, "Predicates for varchar columns are not supported"); } if (varcharType.getLength().isPresent()) { int length = varcharType.getLength().get(); - return (blockBuilder) -> varcharType.writeSlice(blockBuilder, Slices.utf8Slice(faker.lorem().maxLengthSentence(random.nextInt(length)))); + return (blockBuilder) -> varcharType.writeSlice(blockBuilder, boundedSentenceGenerator.get(random.nextInt(length))); } - return (blockBuilder) -> varcharType.writeSlice(blockBuilder, Slices.utf8Slice(faker.lorem().sentence(3 + random.nextInt(38)))); + return (blockBuilder) -> varcharType.writeSlice(blockBuilder, sentenceGenerator.get()); } if (type instanceof CharType charType) { - if (!range.isAll()) { + if (!genericRange.isAll()) { throw new TrinoException(INVALID_ROW_FILTER, "Predicates for char columns are not supported"); } - return (blockBuilder) -> charType.writeSlice(blockBuilder, Slices.utf8Slice(faker.lorem().maxLengthSentence(charType.getLength()))); + return (blockBuilder) -> charType.writeSlice(blockBuilder, boundedSentenceGenerator.get(charType.getLength())); } // not supported: ROW, ARRAY, MAP, JSON if (type instanceof IpAddressType) { - return generateIpV4(range); + return generateIpV4(genericRange); } // not supported: GEOMETRY if (type instanceof UuidType) { - return generateUUID(range); + return generateUUID(genericRange); } throw new IllegalArgumentException("Unsupported type " + type); @@ -441,116 +530,305 @@ private ObjectWriter objectWriter(Type type) throw new IllegalArgumentException("Unsupported type " + type); } - private long generateLong(Range range, long factor) + private Generator decimalGenerator(Range genericRange, DecimalType decimalType) { - return generateLongDefaults(range, factor, Long.MIN_VALUE, Long.MAX_VALUE); + if (decimalType.isShort()) { + ShortDecimalRange range = ShortDecimalRange.of(genericRange, decimalType.getPrecision()); + return (blockBuilder) -> decimalType.writeLong(blockBuilder, numberBetween(range.low, range.high)); + } + Int128Range range = Int128Range.of(genericRange); + BigInteger currentRange = BigInteger.valueOf(Long.MAX_VALUE); + BigInteger desiredRange = range.high.toBigInteger().subtract(range.low.toBigInteger()); + return (blockBuilder) -> decimalType.writeObject(blockBuilder, Int128.valueOf( + new BigInteger(63, random).multiply(desiredRange).divide(currentRange).add(range.low.toBigInteger()))); } - private long generateLongDefaults(Range range, long factor, long min, long max) + private Generator timestampGenerator(Range genericRange, TimestampType tzType) { - return faker.number().numberBetween( - roundDiv((long) range.getLowValue().orElse(min), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), - // TODO does the inclusion only apply to positive numbers? - roundDiv((long) range.getHighValue().orElse(max), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0)) * factor; + if (tzType.isShort()) { + long factor = POWERS_OF_TEN[6 - tzType.getPrecision()]; + LongRange range = LongRange.of(genericRange, factor); + return (blockBuilder) -> tzType.writeLong(blockBuilder, numberBetween(range.low, range.high) * factor); + } + LongTimestampRange range = LongTimestampRange.of(genericRange, tzType.getPrecision()); + if (tzType.getPrecision() <= 6) { + return (blockBuilder) -> { + long epochMicros = numberBetween(range.low.getEpochMicros(), range.high.getEpochMicros()); + tzType.writeObject(blockBuilder, new LongTimestamp(epochMicros * range.factor, 0)); + }; + } + return (blockBuilder) -> { + long epochMicros = numberBetween(range.low.getEpochMicros(), range.high.getEpochMicros()); + int picosOfMicro; + if (epochMicros == range.low.getEpochMicros()) { + picosOfMicro = numberBetween( + range.low.getPicosOfMicro(), + range.low.getEpochMicros() == range.high.getEpochMicros() ? + range.high.getPicosOfMicro() + : (int) POWERS_OF_TEN[tzType.getPrecision() - 6] - 1); + } + else if (epochMicros == range.high.getEpochMicros()) { + picosOfMicro = numberBetween(0, range.high.getPicosOfMicro()); + } + else { + picosOfMicro = numberBetween(0, (int) POWERS_OF_TEN[tzType.getPrecision() - 6] - 1); + } + tzType.writeObject(blockBuilder, new LongTimestamp(epochMicros, picosOfMicro * range.factor)); + }; } - private int generateInt(Range range) + private Generator timestampWithTimeZoneGenerator(Range genericRange, TimestampWithTimeZoneType tzType) { - return (int) faker.number().numberBetween( - (long) range.getLowValue().orElse((long) Integer.MIN_VALUE) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), - (long) range.getHighValue().orElse((long) Integer.MAX_VALUE) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0)); + if (tzType.isShort()) { + ShortTimestampWithTimeZoneRange range = ShortTimestampWithTimeZoneRange.of(genericRange, tzType.getPrecision()); + return (blockBuilder) -> { + long millis = numberBetween(range.low, range.high) * range.factor; + tzType.writeLong(blockBuilder, packDateTimeWithZone(millis, range.defaultTZ)); + }; + } + LongTimestampWithTimeZoneRange range = LongTimestampWithTimeZoneRange.of(genericRange, tzType.getPrecision()); + int picosOfMilliHigh = (int) POWERS_OF_TEN[tzType.getPrecision() - 3] - 1; + return (blockBuilder) -> { + long millis = numberBetween(range.low.getEpochMillis(), range.high.getEpochMillis()); + int picosOfMilli; + if (millis == range.low.getEpochMillis()) { + picosOfMilli = numberBetween( + range.low.getPicosOfMilli(), + range.low.getEpochMillis() == range.high.getEpochMillis() ? + range.high.getPicosOfMilli() + : picosOfMilliHigh); + } + else if (millis == range.high.getEpochMillis()) { + picosOfMilli = numberBetween(0, range.high.getPicosOfMilli()); + } + else { + picosOfMilli = numberBetween(0, picosOfMilliHigh); + } + tzType.writeObject(blockBuilder, fromEpochMillisAndFraction(millis, picosOfMilli * range.factor, range.defaultTZ)); + }; } - private short generateShort(Range range) + private Generator timeWithTimeZoneGenerator(Range genericRange, TimeWithTimeZoneType timeType) { - return (short) faker.number().numberBetween( - (long) range.getLowValue().orElse((long) Short.MIN_VALUE) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), - (long) range.getHighValue().orElse((long) Short.MAX_VALUE) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0)); + if (timeType.isShort()) { + ShortTimeWithTimeZoneRange range = ShortTimeWithTimeZoneRange.of(genericRange, timeType.getPrecision()); + return (blockBuilder) -> { + long nanos = numberBetween(range.low, range.high) * range.factor; + timeType.writeLong(blockBuilder, packTimeWithTimeZone(nanos, range.offsetMinutes)); + }; + } + LongTimeWithTimeZoneRange range = LongTimeWithTimeZoneRange.of(genericRange, timeType.getPrecision()); + return (blockBuilder) -> { + long picoseconds = numberBetween(range.low, range.high) * range.factor; + timeType.writeObject(blockBuilder, new LongTimeWithTimeZone(picoseconds, range.offsetMinutes)); + }; } - private byte generateTiny(Range range) + private record LongRange(long low, long high, long step) { - return (byte) faker.number().numberBetween( - (long) range.getLowValue().orElse((long) Byte.MIN_VALUE) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), - (long) range.getHighValue().orElse((long) Byte.MAX_VALUE) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0)); + static LongRange of(Range range) + { + return of(range, 1, Long.MIN_VALUE, Long.MAX_VALUE, 1); + } + + static LongRange of(Range range, long factor) + { + return of(range, factor, Long.MIN_VALUE, Long.MAX_VALUE, 1); + } + + static LongRange of(Range range, long factor, long step) + { + return of(range, factor, Long.MIN_VALUE, Long.MAX_VALUE, step); + } + + static LongRange of(Range range, long factor, long defaultMin, long defaultMax) + { + return of(range, factor, defaultMin, defaultMax, 1); + } + + static LongRange of(Range range, long factor, long defaultMin, long defaultMax, long step) + { + return new LongRange( + roundDiv((long) range.getLowValue().orElse(defaultMin), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), + roundDiv((long) range.getHighValue().orElse(defaultMax), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0), + step); + } + + long at(long index) + { + return Math.min(low + index * step, high - 1); + } + + long at(long index, long factor) + { + return Math.min(low + roundDiv(index * step, factor), high - 1); + } } - private float generateFloat(Range range) + private record IntRange(int low, int high, long step) { - // TODO normalize ranges in applyFilter, so they always have bounds - float minValue = range.getLowValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MIN_VALUE); - if (!range.isLowUnbounded() && !range.isLowInclusive()) { - minValue = Math.nextUp(minValue); + static IntRange of(Range range) + { + return of(range, Integer.MIN_VALUE, Integer.MAX_VALUE, 1); + } + + static IntRange of(Range range, long step) + { + return of(range, Integer.MIN_VALUE, Integer.MAX_VALUE, step); } - float maxValue = range.getHighValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MAX_VALUE); - if (!range.isHighUnbounded() && !range.isHighInclusive()) { - maxValue = Math.nextDown(maxValue); + + static IntRange of(Range range, long defaultMin, long defaultMax) + { + return of(range, defaultMin, defaultMax, 1); + } + + static IntRange of(Range range, long defaultMin, long defaultMax, long step) + { + return new IntRange( + toIntExact((long) range.getLowValue().orElse(defaultMin)) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), + toIntExact((long) range.getHighValue().orElse(defaultMax)) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0), + step); + } + + long at(long index) + { + return Math.min(low + index * step, high - 1); + } + + long at(long index, long factor) + { + return Math.min(low + roundDiv(index * step, factor), high - 1); } - return minValue + (maxValue - minValue) * random.nextFloat(); } - private double generateDouble(Range range) + private record FloatRange(float low, float high, float step) { - double minValue = (double) range.getLowValue().orElse(Double.MIN_VALUE); - if (!range.isLowUnbounded() && !range.isLowInclusive()) { - minValue = Math.nextUp(minValue); + static FloatRange of(Range range) + { + return of(range, 1); } - double maxValue = (double) range.getHighValue().orElse(Double.MAX_VALUE); - if (!range.isHighUnbounded() && !range.isHighInclusive()) { - maxValue = Math.nextDown(maxValue); + + static FloatRange of(Range range, float step) + { + float low = range.getLowValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MIN_VALUE); + if (!range.isLowUnbounded() && !range.isLowInclusive()) { + low = Math.nextUp(low); + } + float high = range.getHighValue().map(v -> intBitsToFloat(toIntExact((long) v))).orElse(Float.MAX_VALUE); + if (!range.isHighUnbounded() && range.isHighInclusive()) { + high = Math.nextUp(high); + } + return new FloatRange(low, high, step); + } + + float at(long index) + { + return Math.min(low + index * step, Math.nextDown(high)); } - return minValue + (maxValue - minValue) * random.nextDouble(); } - private Generator decimalGenerator(Range range, DecimalType decimalType) + private record DoubleRange(double low, double high, double step) { - if (decimalType.isShort()) { - long min = -999999999999999999L / POWERS_OF_TEN[18 - decimalType.getPrecision()]; - long max = 999999999999999999L / POWERS_OF_TEN[18 - decimalType.getPrecision()]; - return (blockBuilder) -> decimalType.writeLong(blockBuilder, generateLongDefaults(range, 1, min, max)); + static DoubleRange of(Range range) + { + return of(range, 1); } - Int128 low = (Int128) range.getLowValue().orElse(Decimals.MIN_UNSCALED_DECIMAL); - Int128 high = (Int128) range.getHighValue().orElse(Decimals.MAX_UNSCALED_DECIMAL); - if (!range.isLowUnbounded() && !range.isLowInclusive()) { - long[] result = new long[2]; - Int128Math.add(low.getHigh(), low.getLow(), 0, 1, result, 0); - low = Int128.valueOf(result); + + static DoubleRange of(Range range, double step) + { + double low = (double) range.getLowValue().orElse(Double.MIN_VALUE); + if (!range.isLowUnbounded() && !range.isLowInclusive()) { + low = Math.nextUp(low); + } + double high = (double) range.getHighValue().orElse(Double.MAX_VALUE); + if (!range.isHighUnbounded() && range.isHighInclusive()) { + high = Math.nextUp(high); + } + return new DoubleRange(low, high, step); } - if (!range.isHighUnbounded() && range.isHighInclusive()) { - long[] result = new long[2]; - Int128Math.add(high.getHigh(), high.getLow(), 0, 1, result, 0); - high = Int128.valueOf(result); + + double at(long index) + { + return Math.min(low + index * step, Math.nextDown(high)); } + } - BigInteger currentRange = BigInteger.valueOf(Long.MAX_VALUE); - BigInteger desiredRange = high.toBigInteger().subtract(low.toBigInteger()); - Int128 finalLow = low; - return (blockBuilder) -> decimalType.writeObject(blockBuilder, Int128.valueOf( - new BigInteger(63, random).multiply(desiredRange).divide(currentRange).add(finalLow.toBigInteger()))); + private record ShortDecimalRange(long low, long high, long step) + { + static ShortDecimalRange of(Range range, int precision) + { + return of(range, precision, 1); + } + + static ShortDecimalRange of(Range range, int precision, long step) + { + long defaultMin = -999999999999999999L / POWERS_OF_TEN[18 - precision]; + long defaultMax = 999999999999999999L / POWERS_OF_TEN[18 - precision]; + long low = (long) range.getLowValue().orElse(defaultMin) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + long high = (long) range.getHighValue().orElse(defaultMax) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + return new ShortDecimalRange(low, high, step); + } + + long at(long index) + { + return Math.min(low + index * step, high - 1); + } } - private Generator timestampGenerator(Range range, TimestampType tzType) + private record Int128Range(Int128 low, Int128 high, Int128 step) { - if (tzType.isShort()) { - long factor = POWERS_OF_TEN[6 - tzType.getPrecision()]; - return (blockBuilder) -> tzType.writeLong(blockBuilder, generateLong(range, factor)); + static Int128Range of(Range range) + { + return of(range, Int128.ONE); } - LongTimestamp low = (LongTimestamp) range.getLowValue() - .orElse(new LongTimestamp(Long.MIN_VALUE, 0)); - LongTimestamp high = (LongTimestamp) range.getHighValue() - .orElse(new LongTimestamp(Long.MAX_VALUE, PICOSECONDS_PER_MICROSECOND - 1)); - int factor; - if (tzType.getPrecision() <= 6) { - factor = (int) POWERS_OF_TEN[6 - tzType.getPrecision()]; - low = new LongTimestamp( - roundDiv(low.getEpochMicros(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), - 0); - high = new LongTimestamp( - roundDiv(high.getEpochMicros(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0), - 0); + + static Int128Range of(Range range, Int128 step) + { + Int128 low = (Int128) range.getLowValue().orElse(Decimals.MIN_UNSCALED_DECIMAL); + Int128 high = (Int128) range.getHighValue().orElse(Decimals.MAX_UNSCALED_DECIMAL); + if (!range.isLowUnbounded() && !range.isLowInclusive()) { + low = add(low, Int128.ONE); + } + if (!range.isHighUnbounded() && range.isHighInclusive()) { + high = add(high, Int128.ONE); + } + return new Int128Range(low, high, step); } - else { - factor = (int) POWERS_OF_TEN[12 - tzType.getPrecision()]; + + Int128 at(long index) + { + Int128 nextValue = add(low, Int128Math.multiply(Int128.valueOf(index), step)); + Int128 highInclusive = Int128Math.subtract(high, Int128.ONE); + return highInclusive.compareTo(nextValue) < 0 ? highInclusive : nextValue; + } + } + + private static Int128 add(Int128 left, Int128 right) + { + long[] result = new long[2]; + Int128Math.add(left.getHigh(), left.getLow(), right.getHigh(), right.getLow(), result, 0); + return Int128.valueOf(result); + } + + private record LongTimestampRange(LongTimestamp low, LongTimestamp high, int factor, long step) + { + static LongTimestampRange of(Range range, int precision) + { + return of(range, precision, 1); + } + + static LongTimestampRange of(Range range, int precision, long step) + { + LongTimestamp low = (LongTimestamp) range.getLowValue().orElse(new LongTimestamp(Long.MIN_VALUE, 0)); + LongTimestamp high = (LongTimestamp) range.getHighValue().orElse(new LongTimestamp(Long.MAX_VALUE, PICOSECONDS_PER_MICROSECOND - 1)); + int factor; + if (precision <= 6) { + factor = (int) POWERS_OF_TEN[6 - precision]; + low = new LongTimestamp(roundDiv(low.getEpochMicros(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), 0); + high = new LongTimestamp(roundDiv(high.getEpochMicros(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0), 0); + return new LongTimestampRange(low, high, factor, step); + } + factor = (int) POWERS_OF_TEN[12 - precision]; int lowPicosOfMicro = roundDiv(low.getPicosOfMicro(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); low = new LongTimestamp( low.getEpochMicros() - (lowPicosOfMicro < 0 ? 1 : 0), @@ -559,130 +837,179 @@ private Generator timestampGenerator(Range range, TimestampType tzType) high = new LongTimestamp( high.getEpochMicros() + (highPicosOfMicro > factor ? 1 : 0), highPicosOfMicro % factor); + return new LongTimestampRange(low, high, factor, step); + } + + LongTimestamp at(long index, long stepFactor) + { + // TODO support nanosecond increments + // TODO handle exclusive high + long epochMicros = low.getEpochMicros() + roundDiv(index * step, stepFactor); + return new LongTimestamp(step > 0 ? Math.min(epochMicros, high.getEpochMicros()) : Math.max(epochMicros, high.getEpochMicros()), 0); } - LongTimestamp finalLow = low; - LongTimestamp finalHigh = high; - return (blockBuilder) -> { - long epochMicros = faker.number().numberBetween(finalLow.getEpochMicros(), finalHigh.getEpochMicros()); - if (tzType.getPrecision() <= 6) { - epochMicros *= factor; - tzType.writeObject(blockBuilder, new LongTimestamp(epochMicros * factor, 0)); - return; - } - int picosOfMicro; - if (epochMicros == finalLow.getEpochMicros()) { - picosOfMicro = faker.number().numberBetween( - finalLow.getPicosOfMicro(), - finalLow.getEpochMicros() == finalHigh.getEpochMicros() ? - finalHigh.getPicosOfMicro() - : (int) POWERS_OF_TEN[tzType.getPrecision() - 6] - 1); - } - else if (epochMicros == finalHigh.getEpochMicros()) { - picosOfMicro = faker.number().numberBetween(0, finalHigh.getPicosOfMicro()); - } - else { - picosOfMicro = faker.number().numberBetween(0, (int) POWERS_OF_TEN[tzType.getPrecision() - 6] - 1); - } - tzType.writeObject(blockBuilder, new LongTimestamp(epochMicros, picosOfMicro * factor)); - }; } - private Generator timestampWithTimeZoneGenerator(Range range, TimestampWithTimeZoneType tzType) + private record ShortTimestampWithTimeZoneRange(long low, long high, long factor, TimeZoneKey defaultTZ, long step) { - if (tzType.isShort()) { + static ShortTimestampWithTimeZoneRange of(Range range, int precision) + { + return of(range, precision, 1); + } + + static ShortTimestampWithTimeZoneRange of(Range range, int precision, long step) + { TimeZoneKey defaultTZ = range.getLowValue() .map(v -> unpackZoneKey((long) v)) .orElse(range.getHighValue() .map(v -> unpackZoneKey((long) v)) .orElse(TimeZoneKey.UTC_KEY)); - long factor = POWERS_OF_TEN[3 - tzType.getPrecision()]; - return (blockBuilder) -> { - long millis = faker.number().numberBetween( - roundDiv(unpackMillisUtc((long) range.getLowValue().orElse(Long.MIN_VALUE)), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0), - roundDiv(unpackMillisUtc((long) range.getHighValue().orElse(Long.MAX_VALUE)), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0)) * factor; - tzType.writeLong(blockBuilder, packDateTimeWithZone(millis, defaultTZ)); - }; + long factor = POWERS_OF_TEN[3 - precision]; + long low = roundDiv(unpackMillisUtc((long) range.getLowValue().orElse(Long.MIN_VALUE)), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + long high = roundDiv(unpackMillisUtc((long) range.getHighValue().orElse(Long.MAX_VALUE)), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + return new ShortTimestampWithTimeZoneRange(low, high, factor, defaultTZ, step); } - short defaultTZ = range.getLowValue() - .map(v -> ((LongTimestampWithTimeZone) v).getTimeZoneKey()) - .orElse(range.getHighValue() - .map(v -> ((LongTimestampWithTimeZone) v).getTimeZoneKey()) - .orElse(TimeZoneKey.UTC_KEY.getKey())); - LongTimestampWithTimeZone low = (LongTimestampWithTimeZone) range.getLowValue() - .orElse(fromEpochMillisAndFraction(Long.MIN_VALUE >> 12, 0, defaultTZ)); - LongTimestampWithTimeZone high = (LongTimestampWithTimeZone) range.getHighValue() - .orElse(fromEpochMillisAndFraction(Long.MAX_VALUE >> 12, PICOSECONDS_PER_MILLISECOND - 1, defaultTZ)); - if (low.getTimeZoneKey() != high.getTimeZoneKey()) { - throw new TrinoException(INVALID_ROW_FILTER, "Range boundaries for timestamp with time zone columns must have the same time zone"); - } - int factor = (int) POWERS_OF_TEN[12 - tzType.getPrecision()]; - int lowPicosOfMilli = roundDiv(low.getPicosOfMilli(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); - low = fromEpochMillisAndFraction( - low.getEpochMillis() - (lowPicosOfMilli < 0 ? 1 : 0), - (lowPicosOfMilli + factor) % factor, - low.getTimeZoneKey()); - int highPicosOfMilli = roundDiv(high.getPicosOfMilli(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); - high = fromEpochMillisAndFraction( - high.getEpochMillis() + (highPicosOfMilli > factor ? 1 : 0), - highPicosOfMilli % factor, - high.getTimeZoneKey()); - LongTimestampWithTimeZone finalLow = low; - LongTimestampWithTimeZone finalHigh = high; - return (blockBuilder) -> { - long millis = faker.number().numberBetween(finalLow.getEpochMillis(), finalHigh.getEpochMillis()); - int picosOfMilli; - if (millis == finalLow.getEpochMillis()) { - picosOfMilli = faker.number().numberBetween( - finalLow.getPicosOfMilli(), - finalLow.getEpochMillis() == finalHigh.getEpochMillis() ? - finalHigh.getPicosOfMilli() - : (int) POWERS_OF_TEN[tzType.getPrecision() - 3] - 1); - } - else if (millis == finalHigh.getEpochMillis()) { - picosOfMilli = faker.number().numberBetween(0, finalHigh.getPicosOfMilli()); - } - else { - picosOfMilli = faker.number().numberBetween(0, (int) POWERS_OF_TEN[tzType.getPrecision() - 3] - 1); + + long at(long index, long stepFactor) + { + // TODO support nanosecond increments + long millis = low + roundDiv(index * step, factor * stepFactor); + return packDateTimeWithZone((step > 0 ? Math.min(millis, high - 1) : Math.max(millis, high - 1)) * factor, defaultTZ); + } + } + + private record LongTimestampWithTimeZoneRange(LongTimestampWithTimeZone low, LongTimestampWithTimeZone high, int factor, short defaultTZ, long step) + { + static LongTimestampWithTimeZoneRange of(Range range, int precision) + { + return of(range, precision, 1); + } + + static LongTimestampWithTimeZoneRange of(Range range, int precision, long step) + { + short defaultTZ = range.getLowValue() + .map(v -> ((LongTimestampWithTimeZone) v).getTimeZoneKey()) + .orElse(range.getHighValue() + .map(v -> ((LongTimestampWithTimeZone) v).getTimeZoneKey()) + .orElse(TimeZoneKey.UTC_KEY.getKey())); + LongTimestampWithTimeZone low = (LongTimestampWithTimeZone) range.getLowValue().orElse(fromEpochMillisAndFraction(Long.MIN_VALUE >> 12, 0, defaultTZ)); + LongTimestampWithTimeZone high = (LongTimestampWithTimeZone) range.getHighValue().orElse(fromEpochMillisAndFraction(Long.MAX_VALUE >> 12, PICOSECONDS_PER_MILLISECOND - 1, defaultTZ)); + if (low.getTimeZoneKey() != high.getTimeZoneKey()) { + throw new TrinoException(INVALID_ROW_FILTER, "Range boundaries for timestamp with time zone columns must have the same time zone"); } - tzType.writeObject(blockBuilder, fromEpochMillisAndFraction(millis, picosOfMilli * factor, defaultTZ)); - }; + int factor = (int) POWERS_OF_TEN[12 - precision]; + int lowPicosOfMilli = roundDiv(low.getPicosOfMilli(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + low = fromEpochMillisAndFraction( + low.getEpochMillis() - (lowPicosOfMilli < 0 ? 1 : 0), + (lowPicosOfMilli + factor) % factor, + low.getTimeZoneKey()); + int highPicosOfMilli = roundDiv(high.getPicosOfMilli(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + high = fromEpochMillisAndFraction( + high.getEpochMillis() + (highPicosOfMilli > factor ? 1 : 0), + highPicosOfMilli % factor, + high.getTimeZoneKey()); + return new LongTimestampWithTimeZoneRange(low, high, factor, defaultTZ, step); + } + + LongTimestampWithTimeZone at(long index, long stepFactor) + { + // TODO support nanosecond increments + // TODO handle exclusive high + long millis = low.getEpochMillis() + roundDiv(index * step, stepFactor); + return fromEpochMillisAndFraction(step > 0 ? Math.min(millis, high.getEpochMillis()) : Math.max(millis, high.getEpochMillis()), 0, defaultTZ); + } } - private Generator timeWithTimeZoneGenerator(Range range, TimeWithTimeZoneType timeType) + private record ShortTimeWithTimeZoneRange(long low, long high, long factor, int offsetMinutes, long step) { - if (timeType.isShort()) { + static ShortTimeWithTimeZoneRange of(Range range, int precision) + { + return of(range, precision, 1); + } + + static ShortTimeWithTimeZoneRange of(Range range, int precision, long step) + { int offsetMinutes = range.getLowValue() .map(v -> unpackOffsetMinutes((long) v)) .orElse(range.getHighValue() .map(v -> unpackOffsetMinutes((long) v)) .orElse(0)); - long factor = POWERS_OF_TEN[9 - timeType.getPrecision()]; + long factor = POWERS_OF_TEN[9 - precision]; long low = roundDiv(range.getLowValue().map(v -> unpackTimeNanos((long) v)).orElse(0L), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); long high = roundDiv(range.getHighValue().map(v -> unpackTimeNanos((long) v)).orElse(NANOSECONDS_PER_DAY), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); - return (blockBuilder) -> { - long nanos = faker.number().numberBetween(low, high) * factor; - timeType.writeLong(blockBuilder, packTimeWithTimeZone(nanos, offsetMinutes)); - }; + return new ShortTimeWithTimeZoneRange(low, high, factor, offsetMinutes, step); + } + + long at(long index) + { + long nanos = low + roundDiv(index * step, factor); + return packTimeWithTimeZone((step > 0 ? Math.min(nanos, high - 1) : Math.max(nanos, high - 1)) * factor, offsetMinutes); } - int offsetMinutes = range.getLowValue() - .map(v -> ((LongTimeWithTimeZone) v).getOffsetMinutes()) - .orElse(range.getHighValue() - .map(v -> ((LongTimeWithTimeZone) v).getOffsetMinutes()) - .orElse(0)); - LongTimeWithTimeZone low = (LongTimeWithTimeZone) range.getLowValue() - .orElse(new LongTimeWithTimeZone(0, offsetMinutes)); - LongTimeWithTimeZone high = (LongTimeWithTimeZone) range.getHighValue() - .orElse(new LongTimeWithTimeZone(PICOSECONDS_PER_DAY, offsetMinutes)); - if (low.getOffsetMinutes() != high.getOffsetMinutes()) { - throw new TrinoException(INVALID_ROW_FILTER, "Range boundaries for time with time zone columns must have the same time zone"); - } - int factor = (int) POWERS_OF_TEN[12 - timeType.getPrecision()]; - long longLow = roundDiv(low.getPicoseconds(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); - long longHigh = roundDiv(high.getPicoseconds(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); - return (blockBuilder) -> { - long picoseconds = faker.number().numberBetween(longLow, longHigh) * factor; - timeType.writeObject(blockBuilder, new LongTimeWithTimeZone(picoseconds, offsetMinutes)); - }; + } + + private record LongTimeWithTimeZoneRange(long low, long high, int factor, int offsetMinutes, long step) + { + static LongTimeWithTimeZoneRange of(Range range, int precision) + { + return of(range, precision, 1); + } + + static LongTimeWithTimeZoneRange of(Range range, int precision, long step) + { + int offsetMinutes = range.getLowValue() + .map(v -> ((LongTimeWithTimeZone) v).getOffsetMinutes()) + .orElse(range.getHighValue() + .map(v -> ((LongTimeWithTimeZone) v).getOffsetMinutes()) + .orElse(0)); + LongTimeWithTimeZone low = (LongTimeWithTimeZone) range.getLowValue().orElse(new LongTimeWithTimeZone(0, offsetMinutes)); + LongTimeWithTimeZone high = (LongTimeWithTimeZone) range.getHighValue().orElse(new LongTimeWithTimeZone(PICOSECONDS_PER_DAY, offsetMinutes)); + if (low.getOffsetMinutes() != high.getOffsetMinutes()) { + throw new TrinoException(INVALID_ROW_FILTER, "Range boundaries for time with time zone columns must have the same time zone"); + } + int factor = (int) POWERS_OF_TEN[12 - precision]; + long longLow = roundDiv(low.getPicoseconds(), factor) + (!range.isLowUnbounded() && !range.isLowInclusive() ? 1 : 0); + long longHigh = roundDiv(high.getPicoseconds(), factor) + (!range.isHighUnbounded() && range.isHighInclusive() ? 1 : 0); + return new LongTimeWithTimeZoneRange(longLow, longHigh, factor, offsetMinutes, step); + } + + LongTimeWithTimeZone at(long index) + { + long picoseconds = low + roundDiv(index * step, factor); + return new LongTimeWithTimeZone((step > 0 ? Math.min(picoseconds, high - 1) : Math.max(picoseconds, high - 1)) * factor, offsetMinutes); + } + } + + private int numberBetween(int min, int max) + { + if (min == max) { + return min; + } + final int realMin = Math.min(min, max); + final int realMax = Math.max(min, max); + final int amplitude = realMax - realMin; + if (amplitude >= 0) { + return random.nextInt(amplitude) + realMin; + } + // handle overflow + return (int) numberBetween(realMin, (long) realMax); + } + + private long numberBetween(long min, long max) + { + if (min == max) { + return min; + } + final long realMin = Math.min(min, max); + final long realMax = Math.max(min, max); + final long amplitude = realMax - realMin; + if (amplitude >= 0) { + return random.nextLong(amplitude) + realMin; + } + // handle overflow + final BigDecimal bigMin = BigDecimal.valueOf(min); + final BigDecimal bigMax = BigDecimal.valueOf(max); + final BigDecimal randomValue = BigDecimal.valueOf(random.nextDouble()); + + return bigMin.add(bigMax.subtract(bigMin).multiply(randomValue)).longValue(); } private Generator generateIpV4(Range range) @@ -728,6 +1055,12 @@ private Generator generateUUID(Range range) }; } + @FunctionalInterface + private interface SequenceWriter + { + void accept(BlockBuilder blockBuilder, long rowId); + } + @FunctionalInterface private interface ObjectWriter { @@ -739,4 +1072,16 @@ private interface Generator { void accept(BlockBuilder blockBuilder); } + + @FunctionalInterface + private interface SentenceGenerator + { + Slice get(); + } + + @FunctionalInterface + private interface BoundedSentenceGenerator + { + Slice get(int maxLength); + } } diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSourceProvider.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSourceProvider.java index a13c89a0d25b..d9eebc39146b 100644 --- a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSourceProvider.java +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerPageSourceProvider.java @@ -66,10 +66,9 @@ public ConnectorPageSource createPageSource( .map(FakerColumnHandle.class::cast) .collect(toImmutableList()); - FakerTableHandle fakerTable = (FakerTableHandle) table; FakerSplit fakerSplit = (FakerSplit) split; Random random = random(fakerSplit.splitNumber()); - return new FakerPageSource(new Faker(locale, random), random, handles, fakerTable.constraint(), fakerSplit.rowsOffset(), fakerSplit.rowsCount()); + return new FakerPageSource(new Faker(locale, random), random, handles, fakerSplit.rowsOffset(), fakerSplit.rowsCount()); } private Random random(long index) diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerTableHandle.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerTableHandle.java index e9535e7126ce..8f821dcbbdc1 100644 --- a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerTableHandle.java +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/FakerTableHandle.java @@ -14,29 +14,21 @@ package io.trino.plugin.faker; -import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ConnectorTableHandle; import io.trino.spi.connector.SchemaTableName; -import io.trino.spi.predicate.TupleDomain; import static java.util.Objects.requireNonNull; -public record FakerTableHandle(SchemaTableName schemaTableName, TupleDomain constraint, long limit) +public record FakerTableHandle(SchemaTableName schemaTableName, long limit) implements ConnectorTableHandle { public FakerTableHandle { requireNonNull(schemaTableName, "schemaTableName is null"); - requireNonNull(constraint, "constraint is null"); - } - - public FakerTableHandle withConstraint(TupleDomain constraint) - { - return new FakerTableHandle(schemaTableName, constraint, limit); } public FakerTableHandle withLimit(long limit) { - return new FakerTableHandle(schemaTableName, constraint, limit); + return new FakerTableHandle(schemaTableName, limit); } } diff --git a/plugin/trino-faker/src/main/java/io/trino/plugin/faker/Literal.java b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/Literal.java new file mode 100644 index 000000000000..410f259bcb0c --- /dev/null +++ b/plugin/trino-faker/src/main/java/io/trino/plugin/faker/Literal.java @@ -0,0 +1,250 @@ +/* + * 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.faker; + +import com.google.common.base.CharMatcher; +import com.google.common.io.BaseEncoding; +import com.google.common.net.InetAddresses; +import io.airlift.slice.Slice; +import io.airlift.slice.Slices; +import io.trino.spi.type.CharType; +import io.trino.spi.type.DecimalType; +import io.trino.spi.type.Int128; +import io.trino.spi.type.TimeType; +import io.trino.spi.type.TimeWithTimeZoneType; +import io.trino.spi.type.TimestampType; +import io.trino.spi.type.TimestampWithTimeZoneType; +import io.trino.spi.type.Type; +import io.trino.spi.type.UuidType; +import io.trino.spi.type.VarcharType; +import io.trino.sql.tree.IntervalLiteral; +import io.trino.type.IpAddressType; + +import java.math.BigInteger; +import java.util.Optional; +import java.util.UUID; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.google.common.base.Preconditions.checkArgument; +import static io.airlift.slice.Slices.utf8Slice; +import static io.airlift.slice.Slices.wrappedBuffer; +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.TinyintType.TINYINT; +import static io.trino.spi.type.UuidType.javaUuidToTrinoUuid; +import static io.trino.spi.type.VarbinaryType.VARBINARY; +import static io.trino.type.DateTimes.parseTime; +import static io.trino.type.DateTimes.parseTimeWithTimeZone; +import static io.trino.type.DateTimes.parseTimestamp; +import static io.trino.type.DateTimes.parseTimestampWithTimeZone; +import static io.trino.type.IntervalDayTimeType.INTERVAL_DAY_TIME; +import static io.trino.type.IntervalYearMonthType.INTERVAL_YEAR_MONTH; +import static io.trino.util.DateTimeUtils.parseDate; +import static io.trino.util.DateTimeUtils.parseDayTimeInterval; +import static io.trino.util.DateTimeUtils.parseYearMonthInterval; +import static java.lang.Float.floatToRawIntBits; +import static java.lang.System.arraycopy; +import static java.util.Locale.ENGLISH; + +public class Literal +{ + private static final CharMatcher WHITESPACE_MATCHER = CharMatcher.whitespace(); + private static final CharMatcher HEX_DIGIT_MATCHER = CharMatcher.inRange('A', 'F') + .or(CharMatcher.inRange('0', '9')) + .precomputed(); + private static final Pattern DECIMAL_PATTERN = Pattern.compile("([+-]?)(\\d(?:_?\\d)*)?(?:\\.(\\d(?:_?\\d)*)?)?"); + + private Literal() {} + + public static Object parse(String value, Type type) + { + if (value == null) { + return null; + } + if (BIGINT.equals(type) || INTEGER.equals(type) || SMALLINT.equals(type) || TINYINT.equals(type)) { + return parseLong(value); + } + if (BOOLEAN.equals(type)) { + return parseBoolean(value); + } + if (DATE.equals(type)) { + return (long) parseDate(value); + } + if (type instanceof DecimalType decimalType) { + return parseDecimal(value, decimalType); + } + if (REAL.equals(type)) { + return (long) floatToRawIntBits(Float.parseFloat(value)); + } + if (DOUBLE.equals(type)) { + return Double.parseDouble(value); + } + // not supported: HYPER_LOG_LOG, QDIGEST, TDIGEST, P4_HYPER_LOG_LOG + if (INTERVAL_DAY_TIME.equals(type)) { + return parseDayTimeInterval(value, IntervalLiteral.IntervalField.SECOND, Optional.empty()); + } + if (INTERVAL_YEAR_MONTH.equals(type)) { + return parseYearMonthInterval(value, IntervalLiteral.IntervalField.MONTH, Optional.empty()); + } + if (type instanceof TimestampType timestampType) { + return parseTimestamp(timestampType.getPrecision(), value); + } + if (type instanceof TimestampWithTimeZoneType timestampWithTimeZoneType) { + return parseTimestampWithTimeZone(timestampWithTimeZoneType.getPrecision(), value); + } + if (type instanceof TimeType) { + return parseTime(value); + } + if (type instanceof TimeWithTimeZoneType timeWithTimeZoneType) { + return parseTimeWithTimeZone(timeWithTimeZoneType.getPrecision(), value); + } + if (VARBINARY.equals(type)) { + return parseBinary(value); + } + if (type instanceof VarcharType || type instanceof CharType) { + return utf8Slice(value); + } + // not supported: ROW, ARRAY, MAP, JSON + if (type instanceof IpAddressType) { + return parseIpAddress(value); + } + // not supported: GEOMETRY + if (type instanceof UuidType) { + return javaUuidToTrinoUuid(UUID.fromString(value)); + } + throw new IllegalArgumentException("Unsupported literal type: " + type); + } + + private static long parseLong(String value) + { + value = value.replace("_", ""); + + if (value.startsWith("0x") || value.startsWith("0X")) { + return Long.parseLong(value.substring(2), 16); + } + else if (value.startsWith("0b") || value.startsWith("0B")) { + return Long.parseLong(value.substring(2), 2); + } + else if (value.startsWith("0o") || value.startsWith("0O")) { + return Long.parseLong(value.substring(2), 8); + } + else { + return Long.parseLong(value); + } + } + + private static Boolean parseBoolean(String value) + { + value = value.toLowerCase(ENGLISH); + checkArgument(value.equals("true") || value.equals("false")); + return value.equals("true"); + } + + /** + * Based on {@link io.trino.spi.type.Decimals#parse}, but doesn't infer the type from the value. + */ + private static Object parseDecimal(String value, DecimalType decimalType) + { + Matcher matcher = DECIMAL_PATTERN.matcher(value); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid DECIMAL value '" + value + "'"); + } + + String sign = getMatcherGroup(matcher, 1); + if (sign.isEmpty()) { + sign = "+"; + } + String integralPart = getMatcherGroup(matcher, 2); + String fractionalPart = getMatcherGroup(matcher, 3); + + if (integralPart.isEmpty() && fractionalPart.isEmpty()) { + throw new IllegalArgumentException("Invalid DECIMAL value '" + value + "'"); + } + + integralPart = stripLeadingZeros(integralPart.replace("_", "")); + fractionalPart = fractionalPart.replace("_", ""); + + String unscaledValue = sign + (integralPart.isEmpty() ? "0" : "") + integralPart + fractionalPart; + if (decimalType.isShort()) { + return Long.parseLong(unscaledValue); + } + return Int128.valueOf(new BigInteger(unscaledValue)); + } + + private static String getMatcherGroup(MatchResult matcher, int group) + { + String groupValue = matcher.group(group); + if (groupValue == null) { + groupValue = ""; + } + return groupValue; + } + + private static String stripLeadingZeros(String number) + { + for (int i = 0; i < number.length(); i++) { + if (number.charAt(i) != '0') { + return number.substring(i); + } + } + + return ""; + } + + private static Slice parseBinary(String value) + { + String hexString = WHITESPACE_MATCHER.removeFrom(value).toUpperCase(ENGLISH); + if (!HEX_DIGIT_MATCHER.matchesAllOf(hexString)) { + throw new IllegalArgumentException("Binary literal can only contain hexadecimal digits"); + } + if (hexString.length() % 2 != 0) { + throw new IllegalArgumentException("Binary literal must contain an even number of digits"); + } + return Slices.wrappedBuffer(BaseEncoding.base16().decode(hexString)); + } + + private static Slice parseIpAddress(String value) + { + byte[] address; + try { + address = InetAddresses.forString(value).getAddress(); + } + catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Cannot cast value to IPADDRESS: " + value); + } + + byte[] bytes; + if (address.length == 4) { + bytes = new byte[16]; + bytes[10] = (byte) 0xff; + bytes[11] = (byte) 0xff; + arraycopy(address, 0, bytes, 12, 4); + } + else if (address.length == 16) { + bytes = address; + } + else { + throw new IllegalArgumentException("Invalid InetAddress length: " + address.length); + } + + return wrappedBuffer(bytes); + } +} diff --git a/plugin/trino-faker/src/test/java/io/trino/plugin/faker/FakerQueryRunner.java b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/FakerQueryRunner.java index b64e2303ede4..f591c4ddff7c 100644 --- a/plugin/trino-faker/src/test/java/io/trino/plugin/faker/FakerQueryRunner.java +++ b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/FakerQueryRunner.java @@ -17,6 +17,7 @@ import io.airlift.log.Level; import io.airlift.log.Logger; import io.airlift.log.Logging; +import io.trino.plugin.tpch.TpchPlugin; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; @@ -67,6 +68,9 @@ public DistributedQueryRunner build() queryRunner.installPlugin(new FakerPlugin()); queryRunner.createCatalog(CATALOG, "faker", properties); + queryRunner.installPlugin(new TpchPlugin()); + queryRunner.createCatalog("tpch", "tpch", ImmutableMap.of()); + return queryRunner; } catch (Exception e) { diff --git a/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerQueries.java b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerQueries.java index cb75ebaeb777..4861a9ece0c8 100644 --- a/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerQueries.java +++ b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerQueries.java @@ -13,6 +13,7 @@ */ package io.trino.plugin.faker; +import com.google.common.collect.ImmutableList; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.H2QueryRunner; import io.trino.testing.QueryAssertions; @@ -21,10 +22,13 @@ import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Map; +import static io.trino.plugin.faker.ColumnInfo.ALLOWED_VALUES_PROPERTY; import static io.trino.plugin.faker.FakerSplitManager.MAX_ROWS_PER_SPLIT; import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_REFERENCE; +import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; final class TestFakerQueries @@ -48,7 +52,7 @@ void testShowTables() @Test void testColumnComment() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "comment", "(id INTEGER, name VARCHAR)")) { + try (TestTable table = newTrinoTable("comment", "(id INTEGER, name VARCHAR)")) { assertUpdate("COMMENT ON COLUMN %s.name IS 'comment text'".formatted(table.getName())); assertQuery("SHOW COLUMNS FROM " + table.getName(), "VALUES ('id', 'integer', '', ''), ('name', 'varchar', '', 'comment text')"); } @@ -57,7 +61,7 @@ void testColumnComment() @Test void testCannotCommentRowId() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "cannot_comment", "(id INTEGER, name VARCHAR)")) { + try (TestTable table = newTrinoTable("cannot_comment", "(id INTEGER, name VARCHAR)")) { assertThat(query("COMMENT ON COLUMN \"%s\".\"$row_id\" IS 'comment text'".formatted(table.getName()))) .failure() .hasErrorCode(INVALID_COLUMN_REFERENCE) @@ -68,152 +72,59 @@ void testCannotCommentRowId() @Test void testSelectFromTable() { - @Language("SQL") - String tableQuery = - """ - CREATE TABLE faker.default.all_types ( - rnd_bigint bigint NOT NULL, - rnd_integer integer NOT NULL, - rnd_smallint smallint NOT NULL, - rnd_tinyint tinyint NOT NULL, - rnd_boolean boolean NOT NULL, - rnd_date date NOT NULL, - rnd_decimal1 decimal NOT NULL, - rnd_decimal2 decimal(18,5) NOT NULL, - rnd_decimal3 decimal(38,0) NOT NULL, - rnd_decimal4 decimal(38,38) NOT NULL, - rnd_decimal5 decimal(5,2) NOT NULL, - rnd_real real NOT NULL, - rnd_double double NOT NULL, - rnd_interval_day_time interval day to second NOT NULL, - rnd_interval_year interval year to month NOT NULL, - rnd_timestamp timestamp NOT NULL, - rnd_timestamp0 timestamp(0) NOT NULL, - rnd_timestamp6 timestamp(6) NOT NULL, - rnd_timestamp9 timestamp(9) NOT NULL, - rnd_timestamptz timestamp with time zone NOT NULL, - rnd_timestamptz0 timestamp(0) with time zone NOT NULL, - rnd_timestamptz6 timestamp(6) with time zone NOT NULL, - rnd_timestamptz9 timestamp(9) with time zone NOT NULL, - rnd_time time NOT NULL, - rnd_time0 time(0) NOT NULL, - rnd_time6 time(6) NOT NULL, - rnd_time9 time(9) NOT NULL, - rnd_timetz time with time zone NOT NULL, - rnd_timetz0 time(0) with time zone NOT NULL, - rnd_timetz6 time(6) with time zone NOT NULL, - rnd_timetz9 time(9) with time zone NOT NULL, - rnd_timetz12 time(12) with time zone NOT NULL, - rnd_varbinary varbinary NOT NULL, - rnd_varchar varchar NOT NULL, - rnd_nvarchar varchar(1000) NOT NULL, - rnd_char char NOT NULL, - rnd_nchar char(1000) NOT NULL, - rnd_ipaddress ipaddress NOT NULL, - rnd_uuid uuid NOT NULL)"""; - assertUpdate(tableQuery); + List testCases = ImmutableList.builder() + .add(new TestDataType("rnd_bigint", "bigint", "count(distinct rnd_bigint)", "1000")) + .add(new TestDataType("rnd_integer", "integer", "count(distinct rnd_integer)", "1000")) + .add(new TestDataType("rnd_smallint", "smallint", "count(rnd_smallint)", "1000")) + .add(new TestDataType("rnd_tinyint", "tinyint", "count(rnd_tinyint)", "1000")) + .add(new TestDataType("rnd_boolean", "boolean", "count(distinct rnd_boolean)", "2")) + .add(new TestDataType("rnd_date", "date", "count(distinct rnd_date)", "1000")) + .add(new TestDataType("rnd_decimal1", "decimal", "count(distinct rnd_decimal1)", "1000")) + .add(new TestDataType("rnd_decimal2", "decimal(18,5)", "count(distinct rnd_decimal2)", "1000")) + .add(new TestDataType("rnd_decimal3", "decimal(38,0)", "count(distinct rnd_decimal3)", "1000")) + .add(new TestDataType("rnd_decimal4", "decimal(38,38)", "count(distinct rnd_decimal4)", "1000")) + .add(new TestDataType("rnd_decimal5", "decimal(5,2)", "count(rnd_decimal5)", "1000")) + .add(new TestDataType("rnd_real", "real", "count(rnd_real)", "1000")) + .add(new TestDataType("rnd_double", "double", "count(distinct rnd_double)", "1000")) + .add(new TestDataType("rnd_interval1", "interval day to second", "count(distinct rnd_interval1)", "1000")) + .add(new TestDataType("rnd_interval2", "interval year to month", "count(distinct rnd_interval2)", "1000")) + .add(new TestDataType("rnd_timestamp", "timestamp", "count(distinct rnd_timestamp)", "1000")) + .add(new TestDataType("rnd_timestamp0", "timestamp(0)", "count(distinct rnd_timestamp0)", "1000")) + .add(new TestDataType("rnd_timestamp6", "timestamp(6)", "count(distinct rnd_timestamp6)", "1000")) + .add(new TestDataType("rnd_timestamp9", "timestamp(9)", "count(distinct rnd_timestamp9)", "1000")) + .add(new TestDataType("rnd_timestamptz", "timestamp with time zone", "count(distinct rnd_timestamptz)", "1000")) + .add(new TestDataType("rnd_timestamptz0", "timestamp(0) with time zone", "count(distinct rnd_timestamptz0)", "1000")) + .add(new TestDataType("rnd_timestamptz6", "timestamp(6) with time zone", "count(distinct rnd_timestamptz6)", "1000")) + .add(new TestDataType("rnd_timestamptz9", "timestamp(9) with time zone", "count(distinct rnd_timestamptz9)", "1000")) + .add(new TestDataType("rnd_time", "time", "count(rnd_time)", "1000")) + .add(new TestDataType("rnd_time0", "time(0)", "count(rnd_time0)", "1000")) + .add(new TestDataType("rnd_time6", "time(6)", "count(distinct rnd_time6)", "1000")) + .add(new TestDataType("rnd_time9", "time(9)", "count(distinct rnd_time9)", "1000")) + .add(new TestDataType("rnd_timetz", "time with time zone", "count(rnd_timetz)", "1000")) + .add(new TestDataType("rnd_timetz0", "time(0) with time zone", "count(rnd_timetz0)", "1000")) + .add(new TestDataType("rnd_timetz6", "time(6) with time zone", "count(distinct rnd_timetz6)", "1000")) + .add(new TestDataType("rnd_timetz9", "time(9) with time zone", "count(distinct rnd_timetz9)", "1000")) + .add(new TestDataType("rnd_timetz12", "time(12) with time zone", "count(distinct rnd_timetz12)", "1000")) + .add(new TestDataType("rnd_varbinary", "varbinary", "count(distinct rnd_varbinary)", "1000")) + .add(new TestDataType("rnd_varchar", "varchar", "count(rnd_varchar)", "1000")) + .add(new TestDataType("rnd_nvarchar", "varchar(1000)", "count(rnd_nvarchar)", "1000")) + .add(new TestDataType("rnd_char", "char", "count(distinct rnd_char)", "19")) + .add(new TestDataType("rnd_nchar", "char(1000)", "count(distinct rnd_nchar)", "1000")) + .add(new TestDataType("rnd_ipaddress", "ipaddress", "count(distinct rnd_ipaddress)", "1000")) + .add(new TestDataType("rnd_uuid", "uuid", "count(distinct rnd_uuid)", "1000")) + .build(); - @Language("SQL") - String testQuery = - """ - SELECT - count(distinct rnd_bigint), - count(distinct rnd_integer), - count(rnd_smallint), - count(rnd_tinyint), - count(distinct rnd_boolean), - count(distinct rnd_date), - count(distinct rnd_decimal1), - count(distinct rnd_decimal2), - count(distinct rnd_decimal3), - count(distinct rnd_decimal4), - count(rnd_decimal5), - count(rnd_real), - count(distinct rnd_double), - count(distinct rnd_interval_day_time), - count(distinct rnd_interval_year), - count(distinct rnd_timestamp), - count(distinct rnd_timestamp0), - count(distinct rnd_timestamp6), - count(distinct rnd_timestamp9), - count(distinct rnd_timestamptz), - count(distinct rnd_timestamptz0), - count(distinct rnd_timestamptz6), - count(distinct rnd_timestamptz9), - count(rnd_time), - count(rnd_time0), - count(distinct rnd_time6), - count(distinct rnd_time9), - count(rnd_timetz), - count(rnd_timetz0), - count(distinct rnd_timetz6), - count(distinct rnd_timetz9), - count(distinct rnd_varbinary), - count(rnd_varchar), - count(rnd_nvarchar), - count(distinct rnd_char), - count(distinct rnd_nchar), - count(distinct rnd_ipaddress), - count(distinct rnd_uuid) - FROM all_types"""; - assertQuery(testQuery, - """ - VALUES ( - 1000, - 1000, - 1000, - 1000, - -- boolean, date - 2, - 1000, - -- decimal - 1000, - 1000, - 1000, - 1000, - 1000, - -- real, double - 1000, - 1000, - -- intervals - 1000, - 1000, - -- timestamps - 1000, - 1000, - 1000, - 1000, - -- timestamps with time zone - 1000, - 1000, - 1000, - 1000, - -- time - 1000, - 1000, - 1000, - 1000, - -- time with time zone - 1000, - 1000, - 1000, - 1000, - -- varbinary, varchar, nvarchar, char, nchar - 1000, - 1000, - 1000, - 19, - 1000, - -- ip address, uuid - 1000, - 1000)"""); - assertUpdate("DROP TABLE faker.default.all_types"); + for (TestDataType testCase : testCases) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "types_" + testCase.name(), "(%s)".formatted(testCase.columnSchema()))) { + assertQuery("SELECT %s FROM %s".formatted(testCase.queryExpression(), table.getName()), "VALUES (%s)".formatted(testCase.expectedValue())); + } + } } @Test void testSelectLimit() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "single_column", "(rnd_bigint bigint NOT NULL)")) { + try (TestTable table = newTrinoTable("single_column", "(rnd_bigint bigint NOT NULL)")) { assertQuery("SELECT count(rnd_bigint) FROM (SELECT rnd_bigint FROM %s LIMIT 5) a".formatted(table.getName()), "VALUES (5)"); @@ -250,7 +161,7 @@ SELECT count(1) @Test void testSelectDefaultTableLimit() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "default_table_limit", "(rnd_bigint bigint NOT NULL) WITH (default_limit = 100)")) { + try (TestTable table = newTrinoTable("default_table_limit", "(rnd_bigint bigint NOT NULL) WITH (default_limit = 100)")) { assertQuery("SELECT count(distinct rnd_bigint) FROM " + table.getName(), "VALUES (100)"); } } @@ -258,7 +169,7 @@ void testSelectDefaultTableLimit() @Test public void selectOnlyNulls() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "only_nulls", "(rnd_bigint bigint) WITH (null_probability = 1.0)")) { + try (TestTable table = newTrinoTable("only_nulls", "(rnd_bigint bigint) WITH (null_probability = 1.0)")) { assertQuery("SELECT count(distinct rnd_bigint) FROM " + table.getName(), "VALUES (0)"); } } @@ -266,7 +177,7 @@ public void selectOnlyNulls() @Test void testSelectGenerator() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "generators", + try (TestTable table = newTrinoTable("generators", """ ( name VARCHAR NOT NULL WITH (generator = '#{Name.first_name} #{Name.last_name}'), @@ -311,543 +222,229 @@ void testSelectFunctions() } @Test - void testSelectRange() + void testSelectRangeProperties() { - @Language("SQL") - String tableQuery = - """ - CREATE TABLE faker.default.all_types_range ( - rnd_bigint bigint NOT NULL, - rnd_integer integer NOT NULL, - rnd_smallint smallint NOT NULL, - rnd_tinyint tinyint NOT NULL, - rnd_boolean boolean NOT NULL, - rnd_date date NOT NULL, - rnd_decimal1 decimal NOT NULL, - rnd_decimal2 decimal(18,5) NOT NULL, - rnd_decimal3 decimal(38,0) NOT NULL, - rnd_decimal4 decimal(38,38) NOT NULL, - rnd_decimal5 decimal(5,2) NOT NULL, - rnd_real real NOT NULL, - rnd_double double NOT NULL, - rnd_interval_day_time interval day to second NOT NULL, - rnd_interval_year interval year to month NOT NULL, - rnd_timestamp timestamp NOT NULL, - rnd_timestamp0 timestamp(0) NOT NULL, - rnd_timestamp6 timestamp(6) NOT NULL, - rnd_timestamp9 timestamp(9) NOT NULL, - rnd_timestamptz timestamp with time zone NOT NULL, - rnd_timestamptz0 timestamp(0) with time zone NOT NULL, - rnd_timestamptz6 timestamp(6) with time zone NOT NULL, - rnd_timestamptz9 timestamp(9) with time zone NOT NULL, - rnd_time time NOT NULL, - rnd_time0 time(0) NOT NULL, - rnd_time6 time(6) NOT NULL, - rnd_time9 time(9) NOT NULL, - rnd_timetz time with time zone NOT NULL, - rnd_timetz0 time(0) with time zone NOT NULL, - rnd_timetz6 time(6) with time zone NOT NULL, - rnd_timetz9 time(9) with time zone NOT NULL, - rnd_timetz12 time(12) with time zone NOT NULL, - rnd_varbinary varbinary NOT NULL, - rnd_varchar varchar NOT NULL, - rnd_nvarchar varchar(1000) NOT NULL, - rnd_char char NOT NULL, - rnd_nchar char(1000) NOT NULL, - rnd_ipaddress ipaddress NOT NULL, - rnd_uuid uuid NOT NULL)"""; - assertUpdate(tableQuery); + // inclusive ranges that produce only 2 values + // high boundary float value obtained using `Math.nextUp((float) 0.0)` + List testCases = ImmutableList.builder() + .add(new TestDataType("rnd_bigint", "bigint", Map.of("min", "0", "max", "1"), "count(distinct rnd_bigint)", "2")) + .add(new TestDataType("rnd_integer", "integer", Map.of("min", "0", "max", "1"), "count(distinct rnd_integer)", "2")) + .add(new TestDataType("rnd_smallint", "smallint", Map.of("min", "0", "max", "1"), "count(distinct rnd_smallint)", "2")) + .add(new TestDataType("rnd_tinyint", "tinyint", Map.of("min", "0", "max", "1"), "count(distinct rnd_tinyint)", "2")) + .add(new TestDataType("rnd_date", "date", Map.of("min", "2022-03-01", "max", "2022-03-02"), "count(distinct rnd_date)", "2")) + .add(new TestDataType("rnd_decimal1", "decimal", Map.of("min", "0", "max", "1"), "count(distinct rnd_decimal1)", "2")) + .add(new TestDataType("rnd_decimal2", "decimal(18,5)", Map.of("min", "0.00000", "max", "0.00001"), "count(distinct rnd_decimal2)", "2")) + .add(new TestDataType("rnd_decimal3", "decimal(38,0)", Map.of("min", "0", "max", "1"), "count(distinct rnd_decimal3)", "2")) + .add(new TestDataType("rnd_decimal4", "decimal(38,38)", Map.of("min", "0.00000000000000000000000000000000000000", "max", "0.00000000000000000000000000000000000001"), "count(distinct rnd_decimal4)", "2")) + .add(new TestDataType("rnd_decimal5", "decimal(5,2)", Map.of("min", "0.00", "max", "0.01"), "count(distinct rnd_decimal5)", "2")) + .add(new TestDataType("rnd_real", "real", Map.of("min", "0.0", "max", "1.4E-45"), "count(distinct rnd_real)", "2")) + .add(new TestDataType("rnd_double", "double", Map.of("min", "0.0", "max", "4.9E-324"), "count(distinct rnd_double)", "2")) + .add(new TestDataType("rnd_interval1", "interval day to second", Map.of("min", "0.000", "max", "0.001"), "count(distinct rnd_interval1)", "2")) + .add(new TestDataType("rnd_interval2", "interval year to month", Map.of("min", "0", "max", "1"), "count(distinct rnd_interval2)", "2")) + .add(new TestDataType("rnd_timestamp", "timestamp", Map.of("min", "2022-03-21 00:00:00.000", "max", "2022-03-21 00:00:00.001"), "count(distinct rnd_timestamp)", "2")) + .add(new TestDataType("rnd_timestamp0", "timestamp(0)", Map.of("min", "2022-03-21 00:00:00", "max", "2022-03-21 00:00:01"), "count(distinct rnd_timestamp0)", "2")) + .add(new TestDataType("rnd_timestamp6", "timestamp(6)", Map.of("min", "2022-03-21 00:00:00.000000", "max", "2022-03-21 00:00:00.000001"), "count(distinct rnd_timestamp6)", "2")) + .add(new TestDataType("rnd_timestamp9", "timestamp(9)", Map.of("min", "2022-03-21 00:00:00.000000000", "max", "2022-03-21 00:00:00.000000001"), "count(distinct rnd_timestamp9)", "2")) + .add(new TestDataType("rnd_timestamptz", "timestamp with time zone", Map.of("min", "2022-03-21 00:00:00.000 +01:00", "max", "2022-03-21 00:00:00.001 +01:00"), "count(distinct rnd_timestamptz)", "2")) + .add(new TestDataType("rnd_timestamptz0", "timestamp(0) with time zone", Map.of("min", "2022-03-21 00:00:00 +01:00", "max", "2022-03-21 00:00:01 +01:00"), "count(distinct rnd_timestamptz0)", "2")) + .add(new TestDataType("rnd_timestamptz6", "timestamp(6) with time zone", Map.of("min", "2022-03-21 00:00:00.000000 +01:00", "max", "2022-03-21 00:00:00.000001 +01:00"), "count(distinct rnd_timestamptz6)", "2")) + .add(new TestDataType("rnd_timestamptz9", "timestamp(9) with time zone", Map.of("min", "2022-03-21 00:00:00.000000000 +01:00", "max", "2022-03-21 00:00:00.000000001 +01:00"), "count(distinct rnd_timestamptz9)", "2")) + .add(new TestDataType("rnd_time", "time", Map.of("min", "01:02:03.456", "max", "01:02:03.457"), "count(distinct rnd_time)", "2")) + .add(new TestDataType("rnd_time0", "time(0)", Map.of("min", "01:02:03", "max", "01:02:04"), "count(distinct rnd_time0)", "2")) + .add(new TestDataType("rnd_time6", "time(6)", Map.of("min", "01:02:03.000456", "max", "01:02:03.000457"), "count(distinct rnd_time6)", "2")) + .add(new TestDataType("rnd_time9", "time(9)", Map.of("min", "01:02:03.000000456", "max", "01:02:03.000000457"), "count(distinct rnd_time9)", "2")) + .add(new TestDataType("rnd_timetz", "time with time zone", Map.of("min", "01:02:03.456 +01:00", "max", "01:02:03.457 +01:00"), "count(distinct rnd_timetz)", "2")) + .add(new TestDataType("rnd_timetz0", "time(0) with time zone", Map.of("min", "01:02:03 +01:00", "max", "01:02:04 +01:00"), "count(distinct rnd_timetz0)", "2")) + .add(new TestDataType("rnd_timetz6", "time(6) with time zone", Map.of("min", "01:02:03.000456 +01:00", "max", "01:02:03.000457 +01:00"), "count(distinct rnd_timetz6)", "2")) + .add(new TestDataType("rnd_timetz9", "time(9) with time zone", Map.of("min", "01:02:03.000000456 +01:00", "max", "01:02:03.000000457 +01:00"), "count(distinct rnd_timetz9)", "2")) + .add(new TestDataType("rnd_timetz12", "time(12) with time zone", Map.of("min", "01:02:03.000000000456 +01:00", "max", "01:02:03.000000000457 +01:00"), "count(distinct rnd_timetz12)", "2")) + .build(); - @Language("SQL") - String testQuery; - - // inclusive ranges (BETWEEN) that produce only 2 values - // obtained using `Math.nextUp((float) 0.0)` - testQuery = - """ - SELECT - count(distinct rnd_bigint), - count(distinct rnd_integer), - count(distinct rnd_smallint), - count(distinct rnd_tinyint), - count(distinct rnd_date), - count(distinct rnd_decimal1), - count(distinct rnd_decimal2), - count(distinct rnd_decimal3), - count(distinct rnd_decimal4), - count(distinct rnd_decimal5), - count(distinct rnd_real), - count(distinct rnd_double), - count(distinct rnd_interval_day_time), - count(distinct rnd_interval_year), - count(distinct rnd_timestamp), - count(distinct rnd_timestamp0), - count(distinct rnd_timestamp6), - count(distinct rnd_timestamp9), - count(distinct rnd_timestamptz), - count(distinct rnd_timestamptz0), - count(distinct rnd_timestamptz6), - count(distinct rnd_timestamptz9), - count(distinct rnd_time), - count(distinct rnd_time0), - count(distinct rnd_time6), - count(distinct rnd_time9), - count(distinct rnd_timetz), - count(distinct rnd_timetz0), - count(distinct rnd_timetz6), - count(distinct rnd_timetz9) - FROM all_types_range - WHERE 1=1 - AND rnd_bigint BETWEEN 0 AND 1 - AND rnd_integer BETWEEN 0 AND 1 - AND rnd_smallint BETWEEN 0 AND 1 - AND rnd_tinyint BETWEEN 0 AND 1 - AND rnd_date BETWEEN DATE '2022-03-01' AND DATE '2022-03-02' - AND rnd_decimal1 BETWEEN 0 AND 1 - AND rnd_decimal2 BETWEEN 0.00000 AND 0.00001 - AND rnd_decimal3 BETWEEN 0 AND 1 - AND rnd_decimal4 BETWEEN DECIMAL '0.00000000000000000000000000000000000000' AND DECIMAL '0.00000000000000000000000000000000000001' - AND rnd_decimal5 BETWEEN 0.00 AND 0.01 - AND rnd_real BETWEEN REAL '0.0' AND REAL '1.4E-45' - AND rnd_double BETWEEN DOUBLE '0.0' AND DOUBLE '4.9E-324' - AND rnd_interval_day_time BETWEEN INTERVAL '0.000' SECOND AND INTERVAL '0.001' SECOND - AND rnd_interval_year BETWEEN INTERVAL '0' MONTH AND INTERVAL '1' MONTH - AND rnd_timestamp BETWEEN TIMESTAMP '2022-03-21 00:00:00.000' AND TIMESTAMP '2022-03-21 00:00:00.001' - AND rnd_timestamp0 BETWEEN TIMESTAMP '2022-03-21 00:00:00' AND TIMESTAMP '2022-03-21 00:00:01' - AND rnd_timestamp6 BETWEEN TIMESTAMP '2022-03-21 00:00:00.000000' AND TIMESTAMP '2022-03-21 00:00:00.000001' - AND rnd_timestamp9 BETWEEN TIMESTAMP '2022-03-21 00:00:00.000000000' AND TIMESTAMP '2022-03-21 00:00:00.000000001' - AND rnd_timestamptz BETWEEN TIMESTAMP '2022-03-21 00:00:00.000 +01:00' AND TIMESTAMP '2022-03-21 00:00:00.001 +01:00' - AND rnd_timestamptz0 BETWEEN TIMESTAMP '2022-03-21 00:00:00 +01:00' AND TIMESTAMP '2022-03-21 00:00:01 +01:00' - AND rnd_timestamptz6 BETWEEN TIMESTAMP '2022-03-21 00:00:00.000000 +01:00' AND TIMESTAMP '2022-03-21 00:00:00.000001 +01:00' - AND rnd_timestamptz9 BETWEEN TIMESTAMP '2022-03-21 00:00:00.000000000 +01:00' AND TIMESTAMP '2022-03-21 00:00:00.000000001 +01:00' - AND rnd_time BETWEEN TIME '01:02:03.456' AND TIME '01:02:03.457' - AND rnd_time0 BETWEEN TIME '01:02:03' AND TIME '01:02:04' - AND rnd_time6 BETWEEN TIME '01:02:03.000456' AND TIME '01:02:03.000457' - AND rnd_time9 BETWEEN TIME '01:02:03.000000456' AND TIME '01:02:03.000000457' - AND rnd_timetz BETWEEN TIME '01:02:03.456 +01:00' AND TIME '01:02:03.457 +01:00' - AND rnd_timetz0 BETWEEN TIME '01:02:03 +01:00' AND TIME '01:02:04 +01:00' - AND rnd_timetz6 BETWEEN TIME '01:02:03.000456 +01:00' AND TIME '01:02:03.000457 +01:00' - AND rnd_timetz9 BETWEEN TIME '01:02:03.000000456 +01:00' AND TIME '01:02:03.000000457 +01:00'\s"""; - assertQuery(testQuery, - """ - VALUES (2, - 2, - 2, - 2, - -- date - 2, - -- decimal - 2, - 2, - 2, - 2, - 2, - -- real, double - 2, - 2, - -- intervals - 2, - 2, - -- timestamps - 2, - 2, - 2, - 2, - -- timestamps with time zone - 2, - 2, - 2, - 2, - -- time - 2, - 2, - 2, - 2, - -- time with time zone - 2, - 2, - 2, - 2) - """); - - // exclusive ranges that produce only 1 value - // obtained using `Math.nextUp((float) 0.0)` - testQuery = - """ - SELECT - count(distinct rnd_bigint), - count(distinct rnd_integer), - count(distinct rnd_smallint), - count(distinct rnd_tinyint), - count(distinct rnd_date), - count(distinct rnd_decimal1), - count(distinct rnd_decimal2), - count(distinct rnd_decimal3), - count(distinct rnd_decimal4), - count(distinct rnd_decimal5), - count(distinct rnd_real), - count(distinct rnd_double), - count(distinct rnd_interval_day_time), - count(distinct rnd_interval_year), - count(distinct rnd_timestamp), - count(distinct rnd_timestamp0), - count(distinct rnd_timestamp6), - count(distinct rnd_timestamp9), - count(distinct rnd_timestamptz), - count(distinct rnd_timestamptz0), - count(distinct rnd_timestamptz6), - count(distinct rnd_timestamptz9), - count(distinct rnd_time), - count(distinct rnd_time0), - count(distinct rnd_time6), - count(distinct rnd_time9), - count(distinct rnd_timetz), - count(distinct rnd_timetz0), - count(distinct rnd_timetz6), - count(distinct rnd_timetz9) - FROM all_types_range - WHERE 1=1 - AND rnd_bigint > 0 AND rnd_bigint < 2 - AND rnd_integer > 0 AND rnd_integer < 2 - AND rnd_smallint > 0 AND rnd_smallint < 2 - AND rnd_tinyint > 0 AND rnd_tinyint < 2 - AND rnd_date > DATE '2022-03-01' AND rnd_date < DATE '2022-03-03' - AND rnd_decimal1 > 0 AND rnd_decimal1 < 2 - AND rnd_decimal2 > 0.00000 AND rnd_decimal2 < 0.00002 - AND rnd_decimal3 > 0 AND rnd_decimal3 < 2 - AND rnd_decimal4 > DECIMAL '0.00000000000000000000000000000000000000' AND rnd_decimal4 < DECIMAL '0.00000000000000000000000000000000000002' - AND rnd_decimal5 > 0.00 AND rnd_decimal5 < 0.02 - AND rnd_real > REAL '0.0' AND rnd_real < REAL '2.8E-45' - AND rnd_double > DOUBLE '0.0' AND rnd_double < DOUBLE '1.0E-323' - AND rnd_interval_day_time > INTERVAL '0.000' SECOND AND rnd_interval_day_time < INTERVAL '0.002' SECOND - AND rnd_interval_year > INTERVAL '0' MONTH AND rnd_interval_year < INTERVAL '2' MONTH - AND rnd_timestamp > TIMESTAMP '2022-03-21 00:00:00.000' AND rnd_timestamp < TIMESTAMP '2022-03-21 00:00:00.002' - AND rnd_timestamp0 > TIMESTAMP '2022-03-21 00:00:00' AND rnd_timestamp0 < TIMESTAMP '2022-03-21 00:00:02' - AND rnd_timestamp6 > TIMESTAMP '2022-03-21 00:00:00.000000' AND rnd_timestamp6 < TIMESTAMP '2022-03-21 00:00:00.000002' - AND rnd_timestamp9 > TIMESTAMP '2022-03-21 00:00:00.000000000' AND rnd_timestamp9 < TIMESTAMP '2022-03-21 00:00:00.000000002' - AND rnd_timestamptz > TIMESTAMP '2022-03-21 00:00:00.000 +01:00' AND rnd_timestamptz < TIMESTAMP '2022-03-21 00:00:00.002 +01:00' - AND rnd_timestamptz0 > TIMESTAMP '2022-03-21 00:00:00 +01:00' AND rnd_timestamptz0 < TIMESTAMP '2022-03-21 00:00:02 +01:00' - AND rnd_timestamptz6 > TIMESTAMP '2022-03-21 00:00:00.000000 +01:00' AND rnd_timestamptz6 < TIMESTAMP '2022-03-21 00:00:00.000002 +01:00' - AND rnd_timestamptz9 > TIMESTAMP '2022-03-21 00:00:00.000000000 +01:00' AND rnd_timestamptz9 < TIMESTAMP '2022-03-21 00:00:00.000000002 +01:00' - AND rnd_time > TIME '01:02:03.456' AND rnd_time < TIME '01:02:03.458' - AND rnd_time0 > TIME '01:02:03' AND rnd_time0 < TIME '01:02:05' - AND rnd_time6 > TIME '01:02:03.000456' AND rnd_time6 < TIME '01:02:03.000458' - AND rnd_time9 > TIME '01:02:03.000000456' AND rnd_time9 < TIME '01:02:03.000000458' - AND rnd_timetz > TIME '01:02:03.456 +01:00' AND rnd_timetz < TIME '01:02:03.458 +01:00' - AND rnd_timetz0 > TIME '01:02:03 +01:00' AND rnd_timetz0 < TIME '01:02:05 +01:00' - AND rnd_timetz6 > TIME '01:02:03.000456 +01:00' AND rnd_timetz6 < TIME '01:02:03.000458 +01:00' - AND rnd_timetz9 > TIME '01:02:03.000000456 +01:00' AND rnd_timetz9 < TIME '01:02:03.000000458 +01:00'\s"""; - assertQuery(testQuery, - """ - VALUES (1, - 1, - 1, - 1, - -- date - 1, - -- decimal - 1, - 1, - 1, - 1, - 1, - -- real, double - 1, - 1, - -- intervals - 1, - 1, - -- timestamps - 1, - 1, - 1, - 1, - -- timestamps with time zone - 1, - 1, - 1, - 1, - -- time - 1, - 1, - 1, - 1, - -- time with time zone - 1, - 1, - 1, - 1) - """); + for (TestDataType testCase : testCases) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "range_small_" + testCase.name(), "(%s)".formatted(testCase.columnSchema()))) { + assertQuery("SELECT %s FROM %s".formatted(testCase.queryExpression(), table.getName()), "VALUES (%s)".formatted(testCase.expectedValue())); + } + } // inclusive range to get the min low bound - testQuery = - """ - SELECT - count(distinct rnd_bigint), - count(distinct rnd_integer), - count(distinct rnd_smallint), - count(distinct rnd_tinyint), - count(distinct rnd_date), - count(distinct rnd_decimal1), - count(distinct rnd_decimal2), - count(distinct rnd_decimal3), - count(distinct rnd_decimal4), - count(distinct rnd_decimal5), - count(distinct rnd_real), - count(distinct rnd_double), - -- interval literals can't represent smallest possible values allowed by the engine - --count(distinct rnd_interval_day_time), - --count(distinct rnd_interval_year), - -- can't count timestamps because their extreme values cannot be expressed as literals - count(distinct rnd_time), - count(distinct rnd_time0), - count(distinct rnd_time6), - count(distinct rnd_time9), - count(distinct rnd_timetz), - count(distinct rnd_timetz0), - count(distinct rnd_timetz6), - count(distinct rnd_timetz9) - FROM all_types_range - WHERE 1=1 - AND rnd_bigint <= -9223372036854775808 - AND rnd_integer <= -2147483648 - AND rnd_smallint <= -32768 - AND rnd_tinyint <= -128 - -- TODO it actually returns -5877641-06-23 - there's definitely some overflow happening in the engine - AND rnd_date <= DATE '-5877641-06-23' - AND rnd_decimal1 <= DECIMAL '-99999999999999999999999999999999999999' - AND rnd_decimal2 <= DECIMAL '-9999999999999.99999' - AND rnd_decimal3 <= DECIMAL '-99999999999999999999999999999999999999' - AND rnd_decimal4 <= DECIMAL '-0.99999999999999999999999999999999999999' - -- TODO it actually retdurns '-999.98' - AND rnd_decimal5 <= DECIMAL '-999.99' - AND rnd_real <= REAL '1.4E-45' - AND rnd_double <= DOUBLE '4.9E-324' - -- interval literals can't represent smallest possible values allowed by the engine - --AND rnd_interval_day_time <= INTERVAL '-2147483647' SECOND - --AND rnd_interval_year <= INTERVAL '-2147483647' MONTH - AND rnd_time <= TIME '00:00:00.000' - AND rnd_time0 <= TIME '00:00:00' - AND rnd_time6 <= TIME '00:00:00.000000' - AND rnd_time9 <= TIME '00:00:00.000000000' - AND rnd_timetz <= TIME '00:00:00.000 +01:00' - AND rnd_timetz0 <= TIME '00:00:00 +01:00' - AND rnd_timetz6 <= TIME '00:00:00.000000 +01:00' - AND rnd_timetz9 <= TIME '00:00:00.000000000 +01:00' - """; - assertQuery(testQuery, - """ - VALUES (1, - 1, - 1, - 1, - -- date - 1, - -- decimal - 1, - 1, - 1, - 1, - 1, - -- real, double - 1, - 1, - -- intervals - --1, - --1, - -- time - 1, - 1, - 1, - 1, - -- time with time zone - 1, - 1, - 1, - 1) - """); + testCases = ImmutableList.builder() + .add(new TestDataType("rnd_bigint", "bigint", Map.of("max", "-9223372036854775808"), "count(distinct rnd_bigint)", "1")) + .add(new TestDataType("rnd_integer", "integer", Map.of("max", "-2147483648"), "count(distinct rnd_integer)", "1")) + .add(new TestDataType("rnd_smallint", "smallint", Map.of("max", "-32768"), "count(distinct rnd_smallint)", "1")) + .add(new TestDataType("rnd_tinyint", "tinyint", Map.of("max", "-128"), "count(distinct rnd_tinyint)", "1")) + .add(new TestDataType("rnd_date", "date", Map.of("max", "-5877641-06-23"), "count(distinct rnd_date)", "1")) + .add(new TestDataType("rnd_decimal1", "decimal", Map.of("max", "-99999999999999999999999999999999999999"), "count(distinct rnd_decimal1)", "1")) + .add(new TestDataType("rnd_decimal2", "decimal(18,5)", Map.of("max", "-9999999999999.99999"), "count(distinct rnd_decimal2)", "1")) + .add(new TestDataType("rnd_decimal3", "decimal(38,0)", Map.of("max", "-99999999999999999999999999999999999999"), "count(distinct rnd_decimal3)", "1")) + .add(new TestDataType("rnd_decimal4", "decimal(38,38)", Map.of("max", "-0.99999999999999999999999999999999999999"), "count(distinct rnd_decimal4)", "1")) + .add(new TestDataType("rnd_decimal5", "decimal(5,2)", Map.of("max", "-999.99"), "count(distinct rnd_decimal5)", "1")) + .add(new TestDataType("rnd_real", "real", Map.of("max", "1.4E-45"), "count(distinct rnd_real)", "1")) + .add(new TestDataType("rnd_double", "double", Map.of("max", "4.9E-324"), "count(distinct rnd_double)", "1")) + // interval literals can't represent smallest possible values allowed by the engine, so they're not included here + // can't test timestamps because their extreme values cannot be expressed as literals + .add(new TestDataType("rnd_time", "time", Map.of("max", "00:00:00.000"), "count(distinct rnd_time)", "1")) + .add(new TestDataType("rnd_time0", "time(0)", Map.of("max", "00:00:00"), "count(distinct rnd_time0)", "1")) + .add(new TestDataType("rnd_time6", "time(6)", Map.of("max", "00:00:00.000000"), "count(distinct rnd_time6)", "1")) + .add(new TestDataType("rnd_time9", "time(9)", Map.of("max", "00:00:00.000000000"), "count(distinct rnd_time9)", "1")) + .add(new TestDataType("rnd_timetz", "time with time zone", Map.of("max", "00:00:00.000 +01:00"), "count(distinct rnd_timetz)", "1")) + .add(new TestDataType("rnd_timetz0", "time(0) with time zone", Map.of("max", "00:00:00 +01:00"), "count(distinct rnd_timetz0)", "1")) + .add(new TestDataType("rnd_timetz6", "time(6) with time zone", Map.of("max", "00:00:00.000000 +01:00"), "count(distinct rnd_timetz6)", "1")) + .add(new TestDataType("rnd_timetz9", "time(9) with time zone", Map.of("max", "00:00:00.000000000 +01:00"), "count(distinct rnd_timetz9)", "1")) + .add(new TestDataType("rnd_timetz12", "time(12) with time zone", Map.of("max", "00:00:00.000000000000 +01:00"), "count(distinct rnd_timetz12)", "1")) + .build(); + + for (TestDataType testCase : testCases) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "range_max_" + testCase.name(), "(%s)".formatted(testCase.columnSchema()))) { + assertQuery("SELECT %s FROM %s".formatted(testCase.queryExpression(), table.getName()), "VALUES (%s)".formatted(testCase.expectedValue())); + } + } // exclusive range to get the max high bound + testCases = ImmutableList.builder() + .add(new TestDataType("rnd_bigint", "bigint", Map.of("min", "9223372036854775807"), "count(distinct rnd_bigint)", "1")) + .add(new TestDataType("rnd_integer", "integer", Map.of("min", "2147483647"), "count(distinct rnd_integer)", "1")) + .add(new TestDataType("rnd_smallint", "smallint", Map.of("min", "32767"), "count(distinct rnd_smallint)", "1")) + .add(new TestDataType("rnd_tinyint", "tinyint", Map.of("min", "127"), "count(distinct rnd_tinyint)", "1")) + .add(new TestDataType("rnd_date", "date", Map.of("min", "5881580-07-11"), "count(distinct rnd_date)", "1")) + .add(new TestDataType("rnd_decimal1", "decimal", Map.of("min", "99999999999999999999999999999999999999"), "count(distinct rnd_decimal1)", "1")) + .add(new TestDataType("rnd_decimal2", "decimal(18,5)", Map.of("min", "9999999999999.99999"), "count(distinct rnd_decimal2)", "1")) + .add(new TestDataType("rnd_decimal3", "decimal(38,0)", Map.of("min", "99999999999999999999999999999999999999"), "count(distinct rnd_decimal3)", "1")) + .add(new TestDataType("rnd_decimal4", "decimal(38,38)", Map.of("min", "0.99999999999999999999999999999999999999"), "count(distinct rnd_decimal4)", "1")) + .add(new TestDataType("rnd_decimal5", "decimal(5,2)", Map.of("min", "999.99"), "count(distinct rnd_decimal5)", "1")) + .add(new TestDataType("rnd_real", "real", Map.of("min", "3.4028235E38"), "count(distinct rnd_real)", "1")) + .add(new TestDataType("rnd_double", "double", Map.of("min", "1.7976931348623157E308"), "count(distinct rnd_double)", "1")) + // interval literals can't represent smallest possible values allowed by the engine, so they're not included here + // can't test timestamps because their extreme values cannot be expressed as literals + .add(new TestDataType("rnd_time", "time", Map.of("min", "23:59:59.999"), "count(distinct rnd_time)", "1")) + .add(new TestDataType("rnd_time0", "time(0)", Map.of("min", "23:59:59"), "count(distinct rnd_time0)", "1")) + .add(new TestDataType("rnd_time6", "time(6)", Map.of("min", "23:59:59.999999"), "count(distinct rnd_time6)", "1")) + .add(new TestDataType("rnd_time9", "time(9)", Map.of("min", "23:59:59.999999999"), "count(distinct rnd_time9)", "1")) + .add(new TestDataType("rnd_timetz", "time with time zone", Map.of("min", "23:59:59.999 +01:00"), "count(distinct rnd_timetz)", "1")) + .add(new TestDataType("rnd_timetz0", "time(0) with time zone", Map.of("min", "23:59:59 +01:00"), "count(distinct rnd_timetz0)", "1")) + .add(new TestDataType("rnd_timetz6", "time(6) with time zone", Map.of("min", "23:59:59.999999 +01:00"), "count(distinct rnd_timetz6)", "1")) + .add(new TestDataType("rnd_timetz9", "time(9) with time zone", Map.of("min", "23:59:59.999999999 +01:00"), "count(distinct rnd_timetz9)", "1")) + .add(new TestDataType("rnd_timetz12", "time(12) with time zone", Map.of("min", "23:59:59.999999999999 +01:00"), "count(distinct rnd_timetz12)", "1")) + .build(); - assertUpdate("DROP TABLE faker.default.all_types_range"); + for (TestDataType testCase : testCases) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "range_min_" + testCase.name(), "(%s)".formatted(testCase.columnSchema()))) { + assertQuery("SELECT %s FROM %s".formatted(testCase.queryExpression(), table.getName()), "VALUES (%s)".formatted(testCase.expectedValue())); + } + } } @Test - void testSelectIn() + void testSelectValuesProperty() { - @Language("SQL") - String tableQuery = - """ - CREATE TABLE faker.default.all_types_in ( - rnd_bigint bigint NOT NULL, - rnd_integer integer NOT NULL, - rnd_smallint smallint NOT NULL, - rnd_tinyint tinyint NOT NULL, - rnd_boolean boolean NOT NULL, - rnd_date date NOT NULL, - rnd_decimal1 decimal NOT NULL, - rnd_decimal2 decimal(18,5) NOT NULL, - rnd_decimal3 decimal(38,0) NOT NULL, - rnd_decimal4 decimal(38,38) NOT NULL, - rnd_decimal5 decimal(5,2) NOT NULL, - rnd_real real NOT NULL, - rnd_double double NOT NULL, - rnd_interval_day_time interval day to second NOT NULL, - rnd_interval_year interval year to month NOT NULL, - rnd_timestamp timestamp NOT NULL, - rnd_timestamp0 timestamp(0) NOT NULL, - rnd_timestamp6 timestamp(6) NOT NULL, - rnd_timestamp9 timestamp(9) NOT NULL, - rnd_timestamptz timestamp with time zone NOT NULL, - rnd_timestamptz0 timestamp(0) with time zone NOT NULL, - rnd_timestamptz6 timestamp(6) with time zone NOT NULL, - rnd_timestamptz9 timestamp(9) with time zone NOT NULL, - rnd_time time NOT NULL, - rnd_time0 time(0) NOT NULL, - rnd_time6 time(6) NOT NULL, - rnd_time9 time(9) NOT NULL, - rnd_timetz time with time zone NOT NULL, - rnd_timetz0 time(0) with time zone NOT NULL, - rnd_timetz6 time(6) with time zone NOT NULL, - rnd_timetz9 time(9) with time zone NOT NULL, - rnd_timetz12 time(12) with time zone NOT NULL, - rnd_varbinary varbinary NOT NULL, - rnd_varchar varchar NOT NULL, - rnd_nvarchar varchar(1000) NOT NULL, - rnd_ipaddress ipaddress NOT NULL, - rnd_uuid uuid NOT NULL)"""; - assertUpdate(tableQuery); + // inclusive ranges that produce only 2 values + // obtained using `Math.nextUp((float) 0.0)` + List testCases = ImmutableList.builder() + .add(new TestDataType("rnd_bigint", "bigint", Map.of("allowed_values", "ARRAY['0', '1']"), "count(distinct rnd_bigint)", "2")) + .add(new TestDataType("rnd_integer", "integer", Map.of("allowed_values", "ARRAY['0', '1']"), "count(distinct rnd_integer)", "2")) + .add(new TestDataType("rnd_smallint", "smallint", Map.of("allowed_values", "ARRAY['0', '1']"), "count(distinct rnd_smallint)", "2")) + .add(new TestDataType("rnd_tinyint", "tinyint", Map.of("allowed_values", "ARRAY['0', '1']"), "count(distinct rnd_tinyint)", "2")) + .add(new TestDataType("rnd_boolean", "boolean", Map.of("allowed_values", "ARRAY['true', 'false']"), "count(distinct rnd_boolean)", "2")) + .add(new TestDataType("rnd_date", "date", Map.of("allowed_values", "ARRAY['2022-03-01', '2022-03-02']"), "count(distinct rnd_date)", "2")) + .add(new TestDataType("rnd_decimal1", "decimal", Map.of("allowed_values", "ARRAY['0', '1']"), "count(distinct rnd_decimal1)", "2")) + .add(new TestDataType("rnd_decimal2", "decimal(18,5)", Map.of("allowed_values", "ARRAY['0.00000', '0.00001']"), "count(distinct rnd_decimal2)", "2")) + .add(new TestDataType("rnd_decimal3", "decimal(38,0)", Map.of("allowed_values", "ARRAY['0', '1']"), "count(distinct rnd_decimal3)", "2")) + .add(new TestDataType("rnd_decimal4", "decimal(38,38)", Map.of("allowed_values", "ARRAY['0.00000000000000000000000000000000000000', '0.00000000000000000000000000000000000001']"), "count(distinct rnd_decimal4)", "2")) + .add(new TestDataType("rnd_decimal5", "decimal(5,2)", Map.of("allowed_values", "ARRAY['0.00', '0.01']"), "count(distinct rnd_decimal5)", "2")) + .add(new TestDataType("rnd_real", "real", Map.of("allowed_values", "ARRAY['0.0', '1.4E-45']"), "count(distinct rnd_real)", "2")) + .add(new TestDataType("rnd_double", "double", Map.of("allowed_values", "ARRAY['0.0', '4.9E-324']"), "count(distinct rnd_double)", "2")) + .add(new TestDataType("rnd_interval1", "interval day to second", Map.of("allowed_values", "ARRAY['0.000', '0.001']"), "count(distinct rnd_interval1)", "2")) + .add(new TestDataType("rnd_interval2", "interval year to month", Map.of("allowed_values", "ARRAY['0', '1']"), "count(distinct rnd_interval2)", "2")) + .add(new TestDataType("rnd_timestamp", "timestamp", Map.of("allowed_values", "ARRAY['2022-03-21 00:00:00.000', '2022-03-21 00:00:00.001']"), "count(distinct rnd_timestamp)", "2")) + .add(new TestDataType("rnd_timestamp0", "timestamp(0)", Map.of("allowed_values", "ARRAY['2022-03-21 00:00:00', '2022-03-21 00:00:01']"), "count(distinct rnd_timestamp0)", "2")) + .add(new TestDataType("rnd_timestamp6", "timestamp(6)", Map.of("allowed_values", "ARRAY['2022-03-21 00:00:00.000000', '2022-03-21 00:00:00.000001']"), "count(distinct rnd_timestamp6)", "2")) + .add(new TestDataType("rnd_timestamp9", "timestamp(9)", Map.of("allowed_values", "ARRAY['2022-03-21 00:00:00.000000000', '2022-03-21 00:00:00.000000001']"), "count(distinct rnd_timestamp9)", "2")) + .add(new TestDataType("rnd_timestamptz", "timestamp with time zone", Map.of("allowed_values", "ARRAY['2022-03-21 00:00:00.000 +01:00', '2022-03-21 00:00:00.001 +01:00']"), "count(distinct rnd_timestamptz)", "2")) + .add(new TestDataType("rnd_timestamptz0", "timestamp(0) with time zone", Map.of("allowed_values", "ARRAY['2022-03-21 00:00:00 +01:00', '2022-03-21 00:00:01 +01:00']"), "count(distinct rnd_timestamptz0)", "2")) + .add(new TestDataType("rnd_timestamptz6", "timestamp(6) with time zone", Map.of("allowed_values", "ARRAY['2022-03-21 00:00:00.000000 +01:00', '2022-03-21 00:00:00.000001 +01:00']"), "count(distinct rnd_timestamptz6)", "2")) + .add(new TestDataType("rnd_timestamptz9", "timestamp(9) with time zone", Map.of("allowed_values", "ARRAY['2022-03-21 00:00:00.000000000 +01:00', '2022-03-21 00:00:00.000000001 +01:00']"), "count(distinct rnd_timestamptz9)", "2")) + .add(new TestDataType("rnd_time", "time", Map.of("allowed_values", "ARRAY['01:02:03.456', '01:02:03.457']"), "count(distinct rnd_time)", "2")) + .add(new TestDataType("rnd_time0", "time(0)", Map.of("allowed_values", "ARRAY['01:02:03', '01:02:04']"), "count(distinct rnd_time0)", "2")) + .add(new TestDataType("rnd_time6", "time(6)", Map.of("allowed_values", "ARRAY['01:02:03.000456', '01:02:03.000457']"), "count(distinct rnd_time6)", "2")) + .add(new TestDataType("rnd_time9", "time(9)", Map.of("allowed_values", "ARRAY['01:02:03.000000456', '01:02:03.000000457']"), "count(distinct rnd_time9)", "2")) + .add(new TestDataType("rnd_timetz", "time with time zone", Map.of("allowed_values", "ARRAY['01:02:03.456 +01:00', '01:02:03.457 +01:00']"), "count(distinct rnd_timetz)", "2")) + .add(new TestDataType("rnd_timetz0", "time(0) with time zone", Map.of("allowed_values", "ARRAY['01:02:03 +01:00', '01:02:04 +01:00']"), "count(distinct rnd_timetz0)", "2")) + .add(new TestDataType("rnd_timetz6", "time(6) with time zone", Map.of("allowed_values", "ARRAY['01:02:03.000456 +01:00', '01:02:03.000457 +01:00']"), "count(distinct rnd_timetz6)", "2")) + .add(new TestDataType("rnd_timetz9", "time(9) with time zone", Map.of("allowed_values", "ARRAY['01:02:03.000000456 +01:00', '01:02:03.000000457 +01:00']"), "count(distinct rnd_timetz9)", "2")) + .add(new TestDataType("rnd_timetz12", "time(12) with time zone", Map.of("allowed_values", "ARRAY['01:02:03.000000000456 +01:00', '01:02:03.000000000457 +01:00']"), "count(distinct rnd_timetz12)", "2")) + .add(new TestDataType("rnd_varbinary", "varbinary", Map.of("allowed_values", "ARRAY['ff', '00']"), "count(distinct rnd_varbinary)", "2")) + .add(new TestDataType("rnd_varchar", "varchar", Map.of("allowed_values", "ARRAY['aa', 'bb']"), "count(distinct rnd_varchar)", "2")) + .add(new TestDataType("rnd_nvarchar", "varchar(1000)", Map.of("allowed_values", "ARRAY['aa', 'bb']"), "count(distinct rnd_nvarchar)", "2")) + .add(new TestDataType("rnd_ipaddress", "ipaddress", Map.of("allowed_values", "ARRAY['0.0.0.0', '1.2.3.4']"), "count(distinct rnd_ipaddress)", "2")) + .add(new TestDataType("rnd_uuid", "uuid", Map.of("allowed_values", "ARRAY['1fc74d96-0216-449b-a145-455578a9eaa5', '3ee49ede-0026-45e4-ba06-08404f794557']"), "count(distinct rnd_uuid)", "2")) + .build(); - @Language("SQL") - String testQuery; + for (TestDataType testCase : testCases) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "values_" + testCase.name(), "(%s)".formatted(testCase.columnSchema()))) { + assertQuery("SELECT %s FROM %s".formatted(testCase.queryExpression(), table.getName()), "VALUES (%s)".formatted(testCase.expectedValue())); + } + } + } - // inclusive ranges (BETWEEN) that produce only 2 values - // obtained using `Math.nextUp((float) 0.0)` - testQuery = - """ - SELECT - count(distinct rnd_bigint), - count(distinct rnd_integer), - count(distinct rnd_smallint), - count(distinct rnd_tinyint), - count(distinct rnd_date), - count(distinct rnd_decimal1), - count(distinct rnd_decimal2), - count(distinct rnd_decimal3), - count(distinct rnd_decimal4), - count(distinct rnd_decimal5), - count(distinct rnd_real), - count(distinct rnd_double), - count(distinct rnd_interval_day_time), - count(distinct rnd_interval_year), - count(distinct rnd_timestamp), - count(distinct rnd_timestamp0), - count(distinct rnd_timestamp6), - count(distinct rnd_timestamp9), - count(distinct rnd_timestamptz), - count(distinct rnd_timestamptz0), - count(distinct rnd_timestamptz6), - count(distinct rnd_timestamptz9), - count(distinct rnd_time), - count(distinct rnd_time0), - count(distinct rnd_time6), - count(distinct rnd_time9), - count(distinct rnd_timetz), - count(distinct rnd_timetz0), - count(distinct rnd_timetz6), - count(distinct rnd_timetz9), - count(distinct rnd_varbinary), - count(distinct rnd_varchar), - count(distinct rnd_nvarchar), - count(distinct rnd_ipaddress), - count(distinct rnd_uuid) - FROM all_types_in - WHERE 1=1 - AND rnd_bigint IN (0, 1) - AND rnd_integer IN (0, 1) - AND rnd_smallint IN (0, 1) - AND rnd_tinyint IN (0, 1) - AND rnd_date IN (DATE '2022-03-01', DATE '2022-03-02') - AND rnd_decimal1 IN (0, 1) - AND rnd_decimal2 IN (0.00000, 0.00001) - AND rnd_decimal3 IN (0, 1) - AND rnd_decimal4 IN (DECIMAL '0.00000000000000000000000000000000000000', DECIMAL '0.00000000000000000000000000000000000001') - AND rnd_decimal5 IN (0.00, 0.01) - AND rnd_real IN (REAL '0.0', REAL '1.4E-45') - AND rnd_double IN (DOUBLE '0.0', DOUBLE '4.9E-324') - AND rnd_interval_day_time IN (INTERVAL '0.000' SECOND, INTERVAL '0.001' SECOND) - AND rnd_interval_year IN (INTERVAL '0' MONTH, INTERVAL '1' MONTH) - AND rnd_timestamp IN (TIMESTAMP '2022-03-21 00:00:00.000', TIMESTAMP '2022-03-21 00:00:00.001') - AND rnd_timestamp0 IN (TIMESTAMP '2022-03-21 00:00:00', TIMESTAMP '2022-03-21 00:00:01') - AND rnd_timestamp6 IN (TIMESTAMP '2022-03-21 00:00:00.000000', TIMESTAMP '2022-03-21 00:00:00.000001') - AND rnd_timestamp9 IN (TIMESTAMP '2022-03-21 00:00:00.000000000', TIMESTAMP '2022-03-21 00:00:00.000000001') - AND rnd_timestamptz IN (TIMESTAMP '2022-03-21 00:00:00.000 +01:00', TIMESTAMP '2022-03-21 00:00:00.001 +01:00') - AND rnd_timestamptz0 IN (TIMESTAMP '2022-03-21 00:00:00 +01:00', TIMESTAMP '2022-03-21 00:00:01 +01:00') - AND rnd_timestamptz6 IN (TIMESTAMP '2022-03-21 00:00:00.000000 +01:00', TIMESTAMP '2022-03-21 00:00:00.000001 +01:00') - AND rnd_timestamptz9 IN (TIMESTAMP '2022-03-21 00:00:00.000000000 +01:00', TIMESTAMP '2022-03-21 00:00:00.000000001 +01:00') - AND rnd_time IN (TIME '01:02:03.456', TIME '01:02:03.457') - AND rnd_time0 IN (TIME '01:02:03', TIME '01:02:04') - AND rnd_time6 IN (TIME '01:02:03.000456', TIME '01:02:03.000457') - AND rnd_time9 IN (TIME '01:02:03.000000456', TIME '01:02:03.000000457') - AND rnd_timetz IN (TIME '01:02:03.456 +01:00', TIME '01:02:03.457 +01:00') - AND rnd_timetz0 IN (TIME '01:02:03 +01:00', TIME '01:02:04 +01:00') - AND rnd_timetz6 IN (TIME '01:02:03.000456 +01:00', TIME '01:02:03.000457 +01:00') - AND rnd_timetz9 IN (TIME '01:02:03.000000456 +01:00', TIME '01:02:03.000000457 +01:00') - AND rnd_varbinary IN (x'ff', x'00') - AND rnd_varchar IN ('aa', 'bb') - AND rnd_nvarchar IN ('aa', 'bb') - AND rnd_ipaddress IN (IPADDRESS '0.0.0.0', IPADDRESS '1.2.3.4') - AND rnd_uuid IN (UUID '1fc74d96-0216-449b-a145-455578a9eaa5', UUID '3ee49ede-0026-45e4-ba06-08404f794557') - """; - assertQuery(testQuery, - """ - VALUES (2, - 2, - 2, - 2, - -- date - 2, - -- decimal - 2, - 2, - 2, - 2, - 2, - -- real, double - 2, - 2, - -- intervals - 2, - 2, - -- timestamps - 2, - 2, - 2, - 2, - -- timestamps with time zone - 2, - 2, - 2, - 2, - -- time - 2, - 2, - 2, - 2, - -- time with time zone - 2, - 2, - 2, - 2, - -- character types - 2, - 2, - 2, - -- ip, uuid - 2, - 2) - """); - - assertUpdate("DROP TABLE faker.default.all_types_in"); + @Test + void testSelectStepProperties() + { + // small step in small ranges that produce only 10 unique values for 1000 rows + List testCases = ImmutableList.builder() + .add(new TestDataType("rnd_bigint", "bigint", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_bigint)", "10")) + .add(new TestDataType("rnd_integer", "integer", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_integer)", "10")) + .add(new TestDataType("rnd_smallint", "smallint", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_smallint)", "10")) + .add(new TestDataType("rnd_tinyint", "tinyint", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_tinyint)", "10")) + .add(new TestDataType("rnd_date", "date", Map.of("min", "2022-03-01", "max", "2022-03-10", "step", "1d"), "count(distinct rnd_date)", "10")) + .add(new TestDataType("rnd_decimal1", "decimal", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_decimal1)", "10")) + .add(new TestDataType("rnd_decimal2", "decimal(18,5)", Map.of("min", "0.00000", "max", "0.00009", "step", "0.00001"), "count(distinct rnd_decimal2)", "10")) + .add(new TestDataType("rnd_decimal3", "decimal(38,0)", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_decimal3)", "10")) + .add(new TestDataType("rnd_decimal4", "decimal(38,38)", Map.of("min", "0.00000000000000000000000000000000000000", "max", "0.00000000000000000000000000000000000009", "step", "0.00000000000000000000000000000000000001"), "count(distinct rnd_decimal4)", "10")) + .add(new TestDataType("rnd_decimal5", "decimal(5,2)", Map.of("min", "0.00", "max", "1.09", "step", "0.01"), "count(distinct rnd_decimal5)", "110")) + .add(new TestDataType("rnd_real", "real", Map.of("min", "0.0", "max", "1.3E-44", "step", "1.4E-45"), "count(distinct rnd_real)", "10")) + .add(new TestDataType("rnd_double", "double", Map.of("min", "0.0", "max", "4.4E-323", "step", "4.9E-324"), "count(distinct rnd_double)", "10")) + .add(new TestDataType("rnd_interval1", "interval day to second", Map.of("min", "0.000", "max", "0.009", "step", "0.001"), "count(distinct rnd_interval1)", "10")) + .add(new TestDataType("rnd_interval2", "interval year to month", Map.of("min", "0", "max", "9", "step", "1"), "count(distinct rnd_interval2)", "10")) + .add(new TestDataType("rnd_timestamp", "timestamp", Map.of("min", "2022-03-21 00:00:00.000", "max", "2022-03-21 00:00:00.009", "step", "1ms"), "count(distinct rnd_timestamp)", "10")) + .add(new TestDataType("rnd_timestamp0", "timestamp(0)", Map.of("min", "2022-03-21 00:00:00", "max", "2022-03-21 00:00:09", "step", "1s"), "count(distinct rnd_timestamp0)", "10")) + .add(new TestDataType("rnd_timestamp6", "timestamp(6)", Map.of("min", "2022-03-21 00:00:00.000000", "max", "2022-03-21 00:00:00.000009", "step", "1us"), "count(distinct rnd_timestamp6)", "10")) + .add(new TestDataType("rnd_timestamp9", "timestamp(9)", Map.of("min", "2022-03-21 00:00:00.000000000", "max", "2022-03-21 00:00:00.000009000", "step", "1us"), "count(distinct rnd_timestamp9)", "10")) + .add(new TestDataType("rnd_timestamptz", "timestamp with time zone", Map.of("min", "2022-03-21 00:00:00.000 +01:00", "max", "2022-03-21 00:00:00.009 +01:00", "step", "1ms"), "count(distinct rnd_timestamptz)", "10")) + .add(new TestDataType("rnd_timestamptz0", "timestamp(0) with time zone", Map.of("min", "2022-03-21 00:00:00 +01:00", "max", "2022-03-21 00:00:09 +01:00", "step", "1s"), "count(distinct rnd_timestamptz0)", "10")) + .add(new TestDataType("rnd_timestamptz6", "timestamp(6) with time zone", Map.of("min", "2022-03-21 00:00:00.000000 +01:00", "max", "2022-03-21 00:00:00.009000 +01:00", "step", "1ms"), "count(distinct rnd_timestamptz6)", "10")) + .add(new TestDataType("rnd_timestamptz9", "timestamp(9) with time zone", Map.of("min", "2022-03-21 00:00:00.000000000 +01:00", "max", "2022-03-21 00:00:00.009000000 +01:00", "step", "1ms"), "count(distinct rnd_timestamptz9)", "10")) + .add(new TestDataType("rnd_time", "time", Map.of("min", "01:02:03.456", "max", "01:02:03.465", "step", "1ms"), "count(distinct rnd_time)", "10")) + .add(new TestDataType("rnd_time0", "time(0)", Map.of("min", "01:02:03", "max", "01:02:12", "step", "1s"), "count(distinct rnd_time0)", "10")) + .add(new TestDataType("rnd_time6", "time(6)", Map.of("min", "01:02:03.000456", "max", "01:02:03.000465", "step", "1us"), "count(distinct rnd_time6)", "10")) + .add(new TestDataType("rnd_time9", "time(9)", Map.of("min", "01:02:03.000000456", "max", "01:02:03.000000465", "step", "1ns"), "count(distinct rnd_time9)", "10")) + .add(new TestDataType("rnd_timetz", "time with time zone", Map.of("min", "01:02:03.456 +01:00", "max", "01:02:03.465 +01:00", "step", "1ms"), "count(distinct rnd_timetz)", "10")) + .add(new TestDataType("rnd_timetz0", "time(0) with time zone", Map.of("min", "01:02:03 +01:00", "max", "01:02:12 +01:00", "step", "1s"), "count(distinct rnd_timetz0)", "10")) + .add(new TestDataType("rnd_timetz6", "time(6) with time zone", Map.of("min", "01:02:03.000456 +01:00", "max", "01:02:03.000465 +01:00", "step", "1us"), "count(distinct rnd_timetz6)", "10")) + .add(new TestDataType("rnd_timetz9", "time(9) with time zone", Map.of("min", "01:02:03.000000456 +01:00", "max", "01:02:03.000000465 +01:00", "step", "1ns"), "count(distinct rnd_timetz9)", "10")) + .add(new TestDataType("rnd_timetz12", "time(12) with time zone", Map.of("min", "01:02:03.000000000456 +01:00", "max", "01:02:03.000000009456 +01:00", "step", "1ns"), "count(distinct rnd_timetz12)", "10")) + .build(); + + for (TestDataType testCase : testCases) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "step_small_" + testCase.name(), "(%s)".formatted(testCase.columnSchema()))) { + assertQuery("SELECT %s FROM %s".formatted(testCase.queryExpression(), table.getName()), "VALUES (%s)".formatted(testCase.expectedValue())); + } + } + } + + private record TestDataType(String name, String type, Map properties, String queryExpression, String expectedValue) + { + public TestDataType(String name, String type, String queryExpression, String expectedValue) + { + this(name, type, Map.of(), queryExpression, expectedValue); + } + + String columnSchema() + { + String propertiesSchema = properties.entrySet().stream() + .map(entry -> "\"%s\" = %s".formatted( + entry.getKey(), + entry.getKey().equals(ALLOWED_VALUES_PROPERTY) ? entry.getValue() : "'%s'".formatted(entry.getValue()))) + .collect(joining(", ")); + return "%s %s NOT NULL%s".formatted(name, type, propertiesSchema.isEmpty() ? "" : " WITH (%s)".formatted(propertiesSchema)); + } } } diff --git a/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerSplitManager.java b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerSplitManager.java index 898d16cf25fd..c5a4be4a5e08 100644 --- a/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerSplitManager.java +++ b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerSplitManager.java @@ -18,7 +18,6 @@ import io.trino.spi.connector.Constraint; import io.trino.spi.connector.DynamicFilter; import io.trino.spi.connector.SchemaTableName; -import io.trino.spi.predicate.TupleDomain; import io.trino.testing.TestingConnectorSession; import io.trino.testing.TestingTransactionHandle; import org.junit.jupiter.api.Test; @@ -39,7 +38,7 @@ void testSplits() ConnectorSplitSource splitSource = new FakerSplitManager().getSplits( TestingTransactionHandle.create(), TestingConnectorSession.SESSION, - new FakerTableHandle(new SchemaTableName("schema", "table"), TupleDomain.all(), expectedRows), + new FakerTableHandle(new SchemaTableName("schema", "table"), expectedRows), DynamicFilter.EMPTY, Constraint.alwaysTrue()); List splits = splitSource.getNextBatch(1_000_000).get().getSplits(); diff --git a/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerViews.java b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerViews.java new file mode 100644 index 000000000000..809bf8de3302 --- /dev/null +++ b/plugin/trino-faker/src/test/java/io/trino/plugin/faker/TestFakerViews.java @@ -0,0 +1,291 @@ +/* + * 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.faker; + +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.QueryRunner; +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.assertj.core.api.Assertions.assertThat; + +final class TestFakerViews + extends AbstractTestQueryFramework +{ + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + return FakerQueryRunner.builder().build(); + } + + @Test + void testView() + { + @Language("SQL") String query = "SELECT orderkey, orderstatus, (totalprice / 2) half FROM tpch.tiny.orders"; + @Language("SQL") String expectedQuery = "SELECT orderkey, orderstatus, (totalprice / 2) half FROM orders"; + + String catalogName = getSession().getCatalog().orElseThrow(); + String schemaName = getSession().getSchema().orElseThrow(); + String testView = "test_view_" + randomNameSuffix(); + String testViewWithComment = "test_view_with_comment_" + randomNameSuffix(); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) // prime the cache, if any + .doesNotContain(testView); + assertUpdate("CREATE VIEW " + testView + " AS SELECT 123 x"); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) + .contains(testView); + assertUpdate("CREATE OR REPLACE VIEW " + testView + " AS " + query); + + assertUpdate("CREATE VIEW " + testViewWithComment + " COMMENT 'view comment' AS SELECT 123 x"); + assertUpdate("CREATE OR REPLACE VIEW " + testViewWithComment + " COMMENT 'view comment updated' AS " + query); + + String testViewOriginal = "test_view_original_" + randomNameSuffix(); + String testViewRenamed = "test_view_renamed_" + randomNameSuffix(); + assertUpdate("CREATE VIEW " + testViewOriginal + " AS " + query); + assertUpdate("ALTER VIEW " + testViewOriginal + " RENAME TO " + testViewRenamed); + + // verify comment + assertThat((String) computeScalar("SHOW CREATE VIEW " + testViewWithComment)).contains("COMMENT 'view comment updated'"); + assertThat(query( + """ + SELECT table_name, comment + FROM system.metadata.table_comments + WHERE catalog_name = '%s' AND schema_name = '%s' + """.formatted(catalogName, schemaName))) + .skippingTypesCheck() + .containsAll("VALUES ('" + testView + "', null), ('" + testViewWithComment + "', 'view comment updated')"); + + assertUpdate("COMMENT ON VIEW " + testViewWithComment + " IS 'view comment updated twice'"); + assertThat((String) computeScalar("SHOW CREATE VIEW " + testViewWithComment)).contains("COMMENT 'view comment updated twice'"); + + // reading + assertQuery("SELECT * FROM " + testView, expectedQuery); + assertQuery("SELECT * FROM " + testViewRenamed, expectedQuery); + assertQuery("SELECT * FROM " + testViewWithComment, expectedQuery); + + assertQuery( + """ + SELECT * + FROM %1$s a + JOIN %1$s b on a.orderkey = b.orderkey + """.formatted(testView), + """ + SELECT * + FROM (%1$s) a + JOIN (%1$s) b ON a.orderkey = b.orderkey + """.formatted(expectedQuery)); + + assertQuery( + """ + WITH orders AS ( + SELECT * + FROM tpch.tiny.orders + LIMIT 0 + ) + SELECT * + FROM %s + """.formatted(testView), + expectedQuery); + + assertQuery("SELECT * FROM %s.%s.%s".formatted(catalogName, schemaName, testView), expectedQuery); + + assertUpdate("DROP VIEW " + testViewWithComment); + + // information_schema.views without table_name filter + assertThat(query( + """ + SELECT table_name, regexp_replace(view_definition, '\\s', '') + FROM information_schema.views + WHERE table_schema = '%s' + """.formatted(schemaName))) + .skippingTypesCheck() + .containsAll("VALUES ('%1$s', '%3$s'), ('%2$s', '%3$s')".formatted(testView, testViewRenamed, query.replaceAll("\\s", ""))); + // information_schema.views with table_name filter + assertQuery( + """ + SELECT table_name, regexp_replace(view_definition, '\\s', '') + FROM information_schema.views + WHERE table_schema = '%s' and table_name IN ('%s', '%s') + """.formatted(schemaName, testView, testViewRenamed), + "VALUES ('%1$s', '%3$s'), ('%2$s', '%3$s')".formatted(testView, testViewRenamed, query.replaceAll("\\s", ""))); + + // table listing + assertThat(query("SHOW TABLES")) + .skippingTypesCheck() + .containsAll("VALUES '%s', '%s'".formatted(testView, testViewRenamed)); + // information_schema.tables without table_name filter + assertThat(query( + """ + SELECT table_name, table_type + FROM information_schema.tables + WHERE table_schema = '%s' + """.formatted(schemaName))) + .skippingTypesCheck() + .containsAll("VALUES ('%s', 'VIEW'), ('%s', 'VIEW')".formatted(testView, testViewRenamed)); + // information_schema.tables with table_name filter + assertQuery( + """ + SELECT table_name, table_type + FROM information_schema.tables + WHERE table_schema = '%s' and table_name IN ('%s', '%s') + """.formatted(schemaName, testView, testViewRenamed), + "VALUES ('%s', 'VIEW'), ('%s', 'VIEW')".formatted(testView, testViewRenamed)); + + // system.jdbc.tables without filter + assertThat(query( + """ + SELECT table_schem, table_name, table_type + FROM system.jdbc.tables + """)) + .skippingTypesCheck() + .containsAll("VALUES ('%1$s', '%2$s', 'VIEW'), ('%1$s', '%3$s', 'VIEW')".formatted(schemaName, testView, testViewRenamed)); + + // system.jdbc.tables with table prefix filter + assertQuery( + """ + SELECT table_schem, table_name, table_type + FROM system.jdbc.tables + WHERE table_cat = '%s' AND table_schem = '%s' AND table_name IN ('%s', '%s') + """.formatted(catalogName, schemaName, testView, testViewRenamed), + "VALUES ('%1$s', '%2$s', 'VIEW'), ('%1$s', '%3$s', 'VIEW')".formatted(schemaName, testView, testViewRenamed)); + + // column listing + assertThat(query("SHOW COLUMNS FROM " + testView)) + .result() + .projected("Column") // column types can very between connectors + .skippingTypesCheck() + .matches("VALUES 'orderkey', 'orderstatus', 'half'"); + + assertThat(query("DESCRIBE " + testView)) + .result() + .projected("Column") // column types can very between connectors + .skippingTypesCheck() + .matches("VALUES 'orderkey', 'orderstatus', 'half'"); + + // information_schema.columns without table_name filter + assertThat(query( + """ + SELECT table_name, column_name + FROM information_schema.columns + WHERE table_schema = '%s' + """.formatted(schemaName))) + .skippingTypesCheck() + .containsAll( + """ + SELECT * + FROM (VALUES '%s', '%s') + CROSS JOIN UNNEST(ARRAY['orderkey', 'orderstatus', 'half']) + """.formatted(testView, testViewRenamed)); + + // information_schema.columns with table_name filter + assertThat(query( + """ + SELECT table_name, column_name + FROM information_schema.columns + WHERE table_schema = '%s' and table_name IN ('%s', '%s') + """.formatted(schemaName, testView, testViewRenamed))) + .skippingTypesCheck() + .containsAll( + """ + SELECT * + FROM (VALUES '%s', '%s') + CROSS JOIN UNNEST(ARRAY['orderkey', 'orderstatus', 'half']) + """.formatted(testView, testViewRenamed)); + + // view-specific listings + assertThat(query( + """ + SELECT table_name + FROM information_schema.views + WHERE table_schema = '%s' + """.formatted(schemaName))) + .skippingTypesCheck() + .containsAll("VALUES '%s', '%s'".formatted(testView, testViewRenamed)); + + // system.jdbc.columns without filter + assertThat(query( + """ + SELECT table_schem, table_name, column_name + FROM system.jdbc.columns + """)) + .skippingTypesCheck() + .containsAll( + """ + SELECT * + FROM (VALUES ('%1$s', '%2$s'), ('%1$s', '%3$s')) + CROSS JOIN UNNEST(ARRAY['orderkey', 'orderstatus', 'half']) + """.formatted(schemaName, testView, testViewRenamed)); + + // system.jdbc.columns with schema filter + assertThat(query( + """ + SELECT table_schem, table_name, column_name + FROM system.jdbc.columns + WHERE table_schem LIKE '%%%s%%' + """.formatted(schemaName))) + .skippingTypesCheck() + .containsAll( + """ + SELECT * + FROM (VALUES ('%1$s', '%2$s'), ('%1$s', '%3$s')) + CROSS JOIN UNNEST(ARRAY['orderkey', 'orderstatus', 'half']) + """.formatted(schemaName, testView, testViewRenamed)); + + // system.jdbc.columns with table filter + assertThat(query( + """ + SELECT table_schem, table_name, column_name + FROM system.jdbc.columns + WHERE table_name LIKE '%%%s%%' OR table_name LIKE '%%%s%%' + """.formatted(testView, testViewRenamed))) + .skippingTypesCheck() + .containsAll( + """ + SELECT * + FROM (VALUES ('%1$s', '%2$s'), ('%1$s', '%3$s')) + CROSS JOIN UNNEST(ARRAY['orderkey', 'orderstatus', 'half']) + """.formatted(schemaName, testView, testViewRenamed)); + + assertUpdate("DROP VIEW " + testView); + assertUpdate("DROP VIEW " + testViewRenamed); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) + .doesNotContainAnyElementsOf(List.of(testView, testViewRenamed, testViewWithComment)); + } + + @Test + void testViewConflicts() + { + String catalogName = getSession().getCatalog().orElseThrow(); + String schemaName = getSession().getSchema().orElseThrow(); + + String testTable = "test_table_" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + testTable + " (orderkey INT, orderstatus VARCHAR(255), half VARCHAR(255))"); + + assertQueryFails("CREATE VIEW " + testTable + " AS SELECT 123 x", "line 1:1: Table already exists: '%s.%s.%s'".formatted(catalogName, schemaName, testTable)); + + String testView = "test_view_" + randomNameSuffix(); + assertUpdate("CREATE VIEW " + testView + " AS SELECT 123 x"); + + assertQueryFails("CREATE VIEW " + testView + " AS SELECT 123 x", "line 1:1: View already exists: '%s.%s.%s'".formatted(catalogName, schemaName, testView)); + assertQueryFails("CREATE TABLE " + testView + " (orderkey INT, orderstatus VARCHAR(255), half VARCHAR(255))", "View '%s.%s' already exists".formatted(schemaName, testView)); + assertQueryFails("ALTER VIEW " + testView + " RENAME TO " + testTable, "line 1:1: Target view '%s.%s.%s' does not exist, but a table with that name exists.".formatted(catalogName, schemaName, testTable)); + assertQueryFails("ALTER TABLE " + testTable + " RENAME TO " + testView, "line 1:1: Target table '%s.%s.%s' does not exist, but a view with that name exists.".formatted(catalogName, schemaName, testView)); + + assertUpdate("DROP VIEW " + testView); + assertUpdate("DROP TABLE " + testTable); + } +} diff --git a/plugin/trino-functions-python/pom.xml b/plugin/trino-functions-python/pom.xml index 83dd5a661c94..3f641714f5db 100644 --- a/plugin/trino-functions-python/pom.xml +++ b/plugin/trino-functions-python/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index 379b37d6b7d3..bea306914efc 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-google-sheets/pom.xml b/plugin/trino-google-sheets/pom.xml index 8f534bd58bfd..32a9754376da 100644 --- a/plugin/trino-google-sheets/pom.xml +++ b/plugin/trino-google-sheets/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index a163377c0638..fdda2afcc447 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveAnalyzeProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveAnalyzeProperties.java index 549fc0c32995..77f25932cbf2 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveAnalyzeProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveAnalyzeProperties.java @@ -28,7 +28,7 @@ import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static io.trino.plugin.hive.HivePartitionKey.HIVE_DEFAULT_DYNAMIC_PARTITION; +import static io.trino.metastore.Partitions.HIVE_DEFAULT_DYNAMIC_PARTITION; import static io.trino.spi.StandardErrorCode.INVALID_ANALYZE_PROPERTY; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConfig.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConfig.java index e27da88622d9..5b392f4bf14f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConfig.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConfig.java @@ -66,8 +66,6 @@ }) public class HiveConfig { - public static final String CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED = "hive.partition-projection-enabled"; - private boolean singleStatementWritesOnly; private DataSize maxSplitSize = DataSize.of(64, MEGABYTE); @@ -1249,7 +1247,7 @@ public boolean isPartitionProjectionEnabled() return partitionProjectionEnabled; } - @Config(CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED) + @Config("hive.partition-projection-enabled") @ConfigDescription("Enables AWS Athena partition projection") public HiveConfig setPartitionProjectionEnabled(boolean enabledAthenaPartitionProjection) { 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 578a6f1e9c93..5f0505f1431f 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 @@ -29,8 +29,6 @@ import io.trino.spi.connector.ConnectorSplitManager; import io.trino.spi.connector.ConnectorTransactionHandle; import io.trino.spi.connector.TableProcedureMetadata; -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; @@ -62,14 +60,11 @@ public class HiveConnector private final List> viewProperties; private final List> columnProperties; private final List> analyzeProperties; - private final List> materializedViewProperties; private final Optional accessControl; private final ClassLoader classLoader; private final HiveTransactionManager transactionManager; - private final Set connectorTableFunctions; - private final FunctionProvider functionProvider; private final boolean singleStatementWritesOnly; public HiveConnector( @@ -88,10 +83,7 @@ public HiveConnector( List> viewProperties, List> columnProperties, List> analyzeProperties, - List> materializedViewProperties, Optional accessControl, - Set connectorTableFunctions, - FunctionProvider functionProvider, boolean singleStatementWritesOnly, ClassLoader classLoader) { @@ -112,10 +104,7 @@ public HiveConnector( this.viewProperties = ImmutableList.copyOf(requireNonNull(viewProperties, "viewProperties is null")); this.columnProperties = ImmutableList.copyOf(requireNonNull(columnProperties, "columnProperties is null")); 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"); } @@ -194,12 +183,6 @@ public List> getColumnProperties() return this.columnProperties; } - @Override - public List> getMaterializedViewProperties() - { - return materializedViewProperties; - } - @Override public ConnectorAccessControl getAccessControl() { @@ -239,18 +222,6 @@ public final void shutdown() lifeCycleManager.stop(); } - @Override - public Optional getFunctionProvider() - { - return Optional.of(functionProvider); - } - - @Override - public Set getTableFunctions() - { - return connectorTableFunctions; - } - @Override public Set getTableProcedures() { 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 2b1eba0220f2..21299c48f0ff 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 @@ -55,7 +55,6 @@ import io.trino.spi.connector.ConnectorSplitManager; import io.trino.spi.connector.MetadataProvider; import io.trino.spi.connector.TableProcedureMetadata; -import io.trino.spi.function.FunctionProvider; import io.trino.spi.procedure.Procedure; import org.weakref.jmx.guice.MBeanModule; @@ -137,7 +136,6 @@ public static Connector createConnector( HiveViewProperties hiveViewProperties = injector.getInstance(HiveViewProperties.class); HiveColumnProperties hiveColumnProperties = injector.getInstance(HiveColumnProperties.class); HiveAnalyzeProperties hiveAnalyzeProperties = injector.getInstance(HiveAnalyzeProperties.class); - HiveMaterializedViewPropertiesProvider hiveMaterializedViewPropertiesProvider = injector.getInstance(HiveMaterializedViewPropertiesProvider.class); Set procedures = injector.getInstance(new Key<>() {}); Set tableProcedures = injector.getInstance(new Key<>() {}); Set systemTableProviders = injector.getInstance(new Key<>() {}); @@ -161,10 +159,7 @@ public static Connector createConnector( hiveViewProperties.getViewProperties(), hiveColumnProperties.getColumnProperties(), hiveAnalyzeProperties.getAnalyzeProperties(), - hiveMaterializedViewPropertiesProvider.getMaterializedViewProperties(), hiveAccessControl, - injector.getInstance(new Key<>() {}), - injector.getInstance(FunctionProvider.class), injector.getInstance(HiveConfig.class).isSingleStatementWritesOnly(), classLoader); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMaterializedViewPropertiesProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMaterializedViewPropertiesProvider.java deleted file mode 100644 index 829465375eab..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMaterializedViewPropertiesProvider.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; - -import io.trino.spi.session.PropertyMetadata; - -import java.util.List; - -public interface HiveMaterializedViewPropertiesProvider -{ - List> getMaterializedViewProperties(); -} 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 6b958cd2c694..254eda802723 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 @@ -24,6 +24,8 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import dev.failsafe.Failsafe; +import dev.failsafe.RetryPolicy; import io.airlift.json.JsonCodec; import io.airlift.log.Logger; import io.airlift.slice.Slice; @@ -43,10 +45,12 @@ import io.trino.metastore.HiveType; import io.trino.metastore.Partition; import io.trino.metastore.PartitionStatistics; +import io.trino.metastore.Partitions; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.SortingColumn; import io.trino.metastore.StorageFormat; import io.trino.metastore.Table; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.metastore.TableInfo; import io.trino.plugin.base.projection.ApplyProjectionUtil; import io.trino.plugin.base.projection.ApplyProjectionUtil.ProjectedColumnRepresentation; @@ -105,7 +109,6 @@ import io.trino.spi.connector.SystemTable; import io.trino.spi.connector.TableColumnsMetadata; import io.trino.spi.connector.TableNotFoundException; -import io.trino.spi.connector.TableScanRedirectApplicationResult; import io.trino.spi.connector.ViewNotFoundException; import io.trino.spi.connector.WriterScalingOptions; import io.trino.spi.expression.ConnectorExpression; @@ -171,7 +174,7 @@ import static io.trino.metastore.HiveBasicStatistics.createEmptyStatistics; import static io.trino.metastore.HiveBasicStatistics.createZeroStatistics; import static io.trino.metastore.HiveType.HIVE_STRING; -import static io.trino.metastore.Partition.toPartitionValues; +import static io.trino.metastore.Partitions.toPartitionValues; import static io.trino.metastore.PrincipalPrivileges.NO_PRIVILEGES; import static io.trino.metastore.PrincipalPrivileges.fromHivePrivilegeInfos; import static io.trino.metastore.StatisticsUpdateMode.MERGE_INCREMENTAL; @@ -319,7 +322,6 @@ import static io.trino.plugin.hive.util.HiveWriteUtils.createPartitionValues; import static io.trino.plugin.hive.util.HiveWriteUtils.isFileCreatedByQuery; import static io.trino.plugin.hive.util.HiveWriteUtils.isWritableType; -import static io.trino.plugin.hive.util.RetryDriver.retry; import static io.trino.plugin.hive.util.Statistics.createComputedStatisticsToPartitionMap; import static io.trino.plugin.hive.util.Statistics.createEmptyPartitionStatistics; import static io.trino.plugin.hive.util.Statistics.fromComputedStatistics; @@ -388,6 +390,12 @@ public class HiveMetadata public static final String MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE = "Modifying Hive table rows is only supported for transactional tables"; + private static final RetryPolicy DELETE_RETRY_POLICY = RetryPolicy.builder() + .withDelay(java.time.Duration.ofSeconds(1)) + .withMaxDuration(java.time.Duration.ofSeconds(30)) + .withMaxAttempts(10) + .build(); + private final CatalogName catalogName; private final SemiTransactionalHiveMetastore metastore; private final boolean autoCommit; @@ -405,7 +413,6 @@ public class HiveMetadata private final boolean hideDeltaLakeTables; private final String trinoVersion; private final HiveStatisticsProvider hiveStatisticsProvider; - private final HiveRedirectionsProvider hiveRedirectionsProvider; private final Set systemTableProviders; private final AccessControlMetadata accessControlMetadata; private final DirectoryLister directoryLister; @@ -433,7 +440,6 @@ public HiveMetadata( JsonCodec partitionUpdateCodec, String trinoVersion, HiveStatisticsProvider hiveStatisticsProvider, - HiveRedirectionsProvider hiveRedirectionsProvider, Set systemTableProviders, AccessControlMetadata accessControlMetadata, DirectoryLister directoryLister, @@ -460,7 +466,6 @@ public HiveMetadata( this.hideDeltaLakeTables = hideDeltaLakeTables; 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"); this.accessControlMetadata = requireNonNull(accessControlMetadata, "accessControlMetadata is null"); this.directoryLister = requireNonNull(directoryLister, "directoryLister is null"); @@ -1272,7 +1277,7 @@ else if (avroSchemaLiteral != null) { 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"); + "Partition projection is disabled. Enable it in configuration by setting " + "hive.partition-projection-enabled" + "=true"); } Map baseProperties = tableProperties.buildOrThrow(); @@ -1687,7 +1692,7 @@ public void finishStatisticsCollection(ConnectorSession session, ConnectorTableH .orElseThrow(() -> new TableNotFoundException(tableName)); partitionValuesList = partitionNames .stream() - .map(Partition::toPartitionValues) + .map(Partitions::toPartitionValues) .collect(toImmutableList()); } @@ -2056,7 +2061,7 @@ public RowChangeParadigm getRowChangeParadigm(ConnectorSession session, Connecto } @Override - public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, RetryMode retryMode) + public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, Map> updateCaseColumns, RetryMode retryMode) { HiveTableHandle hiveTableHandle = (HiveTableHandle) tableHandle; SchemaTableName tableName = hiveTableHandle.getSchemaTableName(); @@ -2687,14 +2692,13 @@ private void finishOptimize(ConnectorSession session, ConnectorTableExecuteHandl if (firstScannedPath.isEmpty()) { firstScannedPath = Optional.of(scannedPath); } - retry().run("delete " + scannedPath, () -> { + Failsafe.with(DELETE_RETRY_POLICY).run(() -> { try { fileSystem.deleteFile(scannedPath); } catch (FileNotFoundException e) { // ignore missing files } - return null; }); someDeleted = true; remainingFilesToDelete.remove(scannedPath); @@ -3248,12 +3252,6 @@ private HiveColumnHandle createProjectedColumnHandle(HiveColumnHandle column, Li column.getComment()); } - @Override - public Optional applyTableScanRedirect(ConnectorSession session, ConnectorTableHandle tableHandle) - { - return hiveRedirectionsProvider.getTableScanRedirection(session, (HiveTableHandle) tableHandle); - } - @Override public Optional applyPartitioning(ConnectorSession session, ConnectorTableHandle tableHandle, Optional partitioningHandle, List columns) { 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 136b9ab09267..72b5aebe420f 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,10 +20,10 @@ import io.airlift.units.Duration; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.fs.DirectoryLister; import io.trino.plugin.hive.fs.TransactionScopeCachingDirectoryListerFactory; 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.security.AccessControlMetadataFactory; import io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider; @@ -39,7 +39,7 @@ import java.util.concurrent.ScheduledExecutorService; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static java.util.Objects.requireNonNull; public class HiveMetadataFactory @@ -68,7 +68,6 @@ public class HiveMetadataFactory private final Executor updateExecutor; private final long maxPartitionDropsPerQuery; private final String trinoVersion; - private final HiveRedirectionsProvider hiveRedirectionsProvider; private final Set systemTableProviders; private final AccessControlMetadataFactory accessControlMetadataFactory; private final Optional hiveTransactionHeartbeatInterval; @@ -96,7 +95,6 @@ public HiveMetadataFactory( LocationService locationService, JsonCodec partitionUpdateCodec, NodeVersion nodeVersion, - HiveRedirectionsProvider hiveRedirectionsProvider, Set systemTableProviders, AccessControlMetadataFactory accessControlMetadataFactory, DirectoryLister directoryLister, @@ -130,7 +128,6 @@ public HiveMetadataFactory( executorService, heartbeatService, nodeVersion.toString(), - hiveRedirectionsProvider, systemTableProviders, accessControlMetadataFactory, directoryLister, @@ -168,7 +165,6 @@ public HiveMetadataFactory( ExecutorService executorService, ScheduledExecutorService heartbeatService, String trinoVersion, - HiveRedirectionsProvider hiveRedirectionsProvider, Set systemTableProviders, AccessControlMetadataFactory accessControlMetadataFactory, DirectoryLister directoryLister, @@ -198,7 +194,6 @@ public HiveMetadataFactory( this.locationService = requireNonNull(locationService, "locationService is null"); this.partitionUpdateCodec = requireNonNull(partitionUpdateCodec, "partitionUpdateCodec is null"); this.trinoVersion = requireNonNull(trinoVersion, "trinoVersion is null"); - this.hiveRedirectionsProvider = requireNonNull(hiveRedirectionsProvider, "hiveRedirectionsProvider is null"); this.systemTableProviders = requireNonNull(systemTableProviders, "systemTableProviders is null"); this.accessControlMetadataFactory = requireNonNull(accessControlMetadataFactory, "accessControlMetadataFactory is null"); this.hiveTransactionHeartbeatInterval = requireNonNull(hiveTransactionHeartbeatInterval, "hiveTransactionHeartbeatInterval is null"); @@ -266,7 +261,6 @@ public TransactionalMetadata create(ConnectorIdentity identity, boolean autoComm partitionUpdateCodec, trinoVersion, new MetastoreHiveStatisticsProvider(metastore), - hiveRedirectionsProvider, systemTableProviders, accessControlMetadataFactory.create(metastore), directoryLister, 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 4aedf0a0786e..253cd16e01b7 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 @@ -13,7 +13,6 @@ */ package io.trino.plugin.hive; -import com.google.common.collect.ImmutableList; import com.google.inject.Binder; import com.google.inject.Key; import com.google.inject.Module; @@ -55,8 +54,6 @@ 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.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; @@ -86,8 +83,6 @@ public void configure(Binder binder) binder.bind(HiveViewProperties.class).in(Scopes.SINGLETON); binder.bind(HiveColumnProperties.class).in(Scopes.SINGLETON); binder.bind(HiveAnalyzeProperties.class).in(Scopes.SINGLETON); - newOptionalBinder(binder, HiveMaterializedViewPropertiesProvider.class) - .setDefault().toInstance(ImmutableList::of); binder.bind(CachingDirectoryLister.class).in(Scopes.SINGLETON); newExporter(binder).export(CachingDirectoryLister.class).withGeneratedName(); @@ -100,15 +95,13 @@ public void configure(Binder binder) Multibinder systemTableProviders = newSetBinder(binder, SystemTableProvider.class); systemTableProviders.addBinding().to(PartitionsSystemTableProvider.class).in(Scopes.SINGLETON); systemTableProviders.addBinding().to(PropertiesSystemTableProvider.class).in(Scopes.SINGLETON); - newOptionalBinder(binder, HiveRedirectionsProvider.class) - .setDefault().to(NoneHiveRedirectionsProvider.class).in(Scopes.SINGLETON); newOptionalBinder(binder, TransactionalMetadataFactory.class) .setDefault().to(HiveMetadataFactory.class).in(Scopes.SINGLETON); binder.bind(TransactionScopeCachingDirectoryListerFactory.class).in(Scopes.SINGLETON); binder.bind(HiveTransactionManager.class).in(Scopes.SINGLETON); binder.bind(ConnectorSplitManager.class).to(HiveSplitManager.class).in(Scopes.SINGLETON); newExporter(binder).export(ConnectorSplitManager.class).as(generator -> generator.generatedNameOf(HiveSplitManager.class)); - newOptionalBinder(binder, ConnectorPageSourceProvider.class).setDefault().to(HivePageSourceProvider.class).in(Scopes.SINGLETON); + binder.bind(ConnectorPageSourceProvider.class).to(HivePageSourceProvider.class).in(Scopes.SINGLETON); binder.bind(ConnectorPageSinkProvider.class).to(HivePageSinkProvider.class).in(Scopes.SINGLETON); binder.bind(ConnectorNodePartitioningProvider.class).to(HiveNodePartitioningProvider.class).in(Scopes.SINGLETON); @@ -148,9 +141,6 @@ public void configure(Binder binder) configBinder(binder).bindConfig(ParquetWriterConfig.class); fileWriterFactoryBinder.addBinding().to(ParquetFileWriterFactory.class).in(Scopes.SINGLETON); - newOptionalBinder(binder, FunctionProvider.class).setDefault().toInstance(new NoopFunctionProvider()); - newSetBinder(binder, ConnectorTableFunction.class); - closingBinder(binder).registerExecutor(ExecutorService.class); closingBinder(binder).registerExecutor(Key.get(ScheduledExecutorService.class, ForHiveTransactionHeartbeats.class)); } 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 e8e987b1c728..d71d24935d34 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 @@ -21,10 +21,10 @@ import io.airlift.json.JsonCodec; import io.airlift.units.DataSize; import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.SortingColumn; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.HivePageSinkMetadataProvider; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.spi.PageIndexerFactory; import io.trino.spi.PageSorter; import io.trino.spi.connector.ConnectorInsertTableHandle; @@ -48,7 +48,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.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newFixedThreadPool; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePartitionKey.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePartitionKey.java index cfc0ee267e2f..a6dd1dcc452a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePartitionKey.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePartitionKey.java @@ -15,14 +15,13 @@ import static io.airlift.slice.SizeOf.estimatedSizeOf; import static io.airlift.slice.SizeOf.instanceSize; +import static io.trino.metastore.Partitions.HIVE_DEFAULT_DYNAMIC_PARTITION; import static java.util.Objects.requireNonNull; public record HivePartitionKey(String name, String value) { private static final int INSTANCE_SIZE = instanceSize(HivePartitionKey.class); - public static final String HIVE_DEFAULT_DYNAMIC_PARTITION = "__HIVE_DEFAULT_PARTITION__"; - public HivePartitionKey { requireNonNull(name, "name is null"); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePartitionManager.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePartitionManager.java index fd2e7dcb873e..1bfd12de825a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePartitionManager.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePartitionManager.java @@ -38,7 +38,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.metastore.Partition.unescapePathName; +import static io.trino.metastore.Partitions.unescapePathName; import static io.trino.plugin.hive.metastore.MetastoreUtil.computePartitionKeyFilter; import static io.trino.plugin.hive.metastore.MetastoreUtil.toPartitionName; import static io.trino.plugin.hive.util.HiveBucketing.getHiveBucketFilter; 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 28d60c873c1a..1cdc3935d84d 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 @@ -65,6 +65,7 @@ import static com.google.common.collect.MoreCollectors.onlyElement; import static io.trino.hive.formats.HiveClassNames.HIVE_IGNORE_KEY_OUTPUT_FORMAT_CLASS; import static io.trino.metastore.AcidOperation.CREATE_TABLE; +import static io.trino.metastore.Partitions.makePartName; import static io.trino.plugin.hive.HiveCompressionCodecs.selectCompressionCodec; import static io.trino.plugin.hive.HiveErrorCode.HIVE_FILESYSTEM_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_METADATA; @@ -84,7 +85,6 @@ import static io.trino.plugin.hive.util.HiveTypeUtil.getType; import static io.trino.plugin.hive.util.HiveUtil.getColumnNames; import static io.trino.plugin.hive.util.HiveUtil.getColumnTypes; -import static io.trino.plugin.hive.util.HiveUtil.makePartName; import static io.trino.plugin.hive.util.HiveWriteUtils.createPartitionValues; import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_TYPES; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoneHiveRedirectionsProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoneHiveRedirectionsProvider.java deleted file mode 100644 index 124fe56dd358..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoneHiveRedirectionsProvider.java +++ /dev/null @@ -1,30 +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.connector.ConnectorSession; -import io.trino.spi.connector.TableScanRedirectApplicationResult; - -import java.util.Optional; - -public class NoneHiveRedirectionsProvider - implements HiveRedirectionsProvider -{ - @Override - public Optional getTableScanRedirection(ConnectorSession session, HiveTableHandle tableHandle) - { - return Optional.empty(); - } -} 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 df99de97138c..1e485066bdba 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 @@ -19,6 +19,7 @@ import io.trino.metastore.HiveMetastore; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.Table; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.metastore.TableInfo; import io.trino.spi.TrinoException; import io.trino.spi.connector.ConnectorSession; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/CachingHiveMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/CachingHiveMetastoreModule.java index d9294b4eb916..5b2e0cc2c7ae 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/CachingHiveMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/CachingHiveMetastoreModule.java @@ -18,33 +18,22 @@ import com.google.inject.Scopes; import com.google.inject.Singleton; import io.airlift.configuration.AbstractConfigurationAwareModule; -import io.trino.plugin.hive.fs.DirectoryLister; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastoreConfig; -import io.trino.plugin.hive.metastore.cache.ImpersonationCachingConfig; -import io.trino.plugin.hive.metastore.cache.SharedHiveMetastoreCache; -import io.trino.plugin.hive.metastore.cache.SharedHiveMetastoreCache.CachingHiveMetastoreFactory; -import io.trino.plugin.hive.metastore.glue.GlueCache; -import io.trino.plugin.hive.procedure.FlushMetadataCacheProcedure; -import io.trino.spi.procedure.Procedure; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; +import io.trino.metastore.cache.CachingHiveMetastore; +import io.trino.metastore.cache.CachingHiveMetastoreConfig; +import io.trino.metastore.cache.ImpersonationCachingConfig; +import io.trino.metastore.cache.SharedHiveMetastoreCache; +import io.trino.metastore.cache.SharedHiveMetastoreCache.CachingHiveMetastoreFactory; import java.util.Optional; -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 CachingHiveMetastoreModule extends AbstractConfigurationAwareModule { - private final boolean installFlushMetadataCacheProcedure; - - public CachingHiveMetastoreModule(boolean installFlushMetadataCacheProcedure) - { - this.installFlushMetadataCacheProcedure = installFlushMetadataCacheProcedure; - } - @Override protected void setup(Binder binder) { @@ -55,12 +44,6 @@ protected void setup(Binder binder) // export under the old name, for backwards compatibility newExporter(binder).export(HiveMetastoreFactory.class) .as(generator -> generator.generatedNameOf(CachingHiveMetastore.class)); - - if (installFlushMetadataCacheProcedure) { - newOptionalBinder(binder, GlueCache.class); - newOptionalBinder(binder, DirectoryLister.class); - newSetBinder(binder, Procedure.class).addBinding().toProvider(FlushMetadataCacheProcedure.class).in(Scopes.SINGLETON); - } } @Provides 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 d3e8b21e4182..7e247958ab31 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 @@ -20,6 +20,8 @@ import com.google.inject.Singleton; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.plugin.hive.AllowHiveTableRename; import io.trino.plugin.hive.HideDeltaLakeTables; import io.trino.plugin.hive.metastore.file.FileMetastoreModule; @@ -54,7 +56,7 @@ protected void setup(Binder binder) bindMetastoreModule("glue-v1", new io.trino.plugin.hive.metastore.glue.v1.GlueMetastoreModule()); } - install(new CachingHiveMetastoreModule(true)); + install(new CachingHiveMetastoreModule()); } private void bindMetastoreModule(String name, Module module) 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 5280dd5bb578..fa851d819ed8 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 @@ -88,12 +88,9 @@ import static io.trino.hive.thrift.metastore.hive_metastoreConstants.META_TABLE_NAME; import static io.trino.hive.thrift.metastore.hive_metastoreConstants.META_TABLE_PARTITION_COLUMNS; import static io.trino.hive.thrift.metastore.hive_metastoreConstants.META_TABLE_PARTITION_COLUMN_TYPES; -import static io.trino.plugin.hive.HiveMetadata.AVRO_SCHEMA_LITERAL_KEY; -import static io.trino.plugin.hive.HiveMetadata.AVRO_SCHEMA_URL_KEY; +import static io.trino.metastore.Partitions.makePartName; import static io.trino.plugin.hive.HiveSplitManager.PRESTO_OFFLINE; -import static io.trino.plugin.hive.HiveStorageFormat.AVRO; import static io.trino.plugin.hive.metastore.SparkMetastoreUtil.getSparkBasicStatistics; -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; @@ -243,15 +240,6 @@ public static ProtectMode getProtectMode(Table table) return getProtectMode(table.getParameters()); } - public static boolean isAvroTableWithSchemaSet(Table table) - { - return AVRO.getSerde().equals(table.getStorage().getStorageFormat().getSerDeNullable()) && - ((table.getParameters().get(AVRO_SCHEMA_URL_KEY) != null || - (table.getStorage().getSerdeParameters().get(AVRO_SCHEMA_URL_KEY) != null)) || - (table.getParameters().get(AVRO_SCHEMA_LITERAL_KEY) != null || - (table.getStorage().getSerdeParameters().get(AVRO_SCHEMA_LITERAL_KEY) != null))); - } - public static String makePartitionName(Table table, Partition partition) { return makePartitionName(table.getPartitionColumns(), partition.getValues()); 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 5f4015494c93..e055c767de1c 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 @@ -51,12 +51,12 @@ import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.StatisticsUpdateMode; import io.trino.metastore.Table; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.metastore.TableInfo; import io.trino.plugin.hive.HiveTableHandle; import io.trino.plugin.hive.LocationHandle.WriteMode; import io.trino.plugin.hive.PartitionNotFoundException; import io.trino.plugin.hive.PartitionUpdateAndMergeResults; -import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.TableInvalidationCallback; import io.trino.plugin.hive.acid.AcidTransaction; import io.trino.plugin.hive.projection.PartitionProjection; @@ -111,7 +111,8 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.airlift.concurrent.MoreFutures.getFutureValue; import static io.trino.metastore.HivePrivilegeInfo.HivePrivilege.OWNERSHIP; -import static io.trino.metastore.Partition.toPartitionValues; +import static io.trino.metastore.Partitions.makePartName; +import static io.trino.metastore.Partitions.toPartitionValues; import static io.trino.metastore.PrincipalPrivileges.NO_PRIVILEGES; import static io.trino.metastore.StatisticsUpdateMode.MERGE_INCREMENTAL; import static io.trino.metastore.StatisticsUpdateMode.OVERWRITE_ALL; @@ -135,7 +136,6 @@ import static io.trino.plugin.hive.metastore.SparkMetastoreUtil.getSparkTableStatistics; import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getPartitionProjectionFromTable; import static io.trino.plugin.hive.util.AcidTables.isTransactionalTable; -import static io.trino.plugin.hive.util.HiveUtil.makePartName; import static io.trino.plugin.hive.util.HiveWriteUtils.isFileCreatedByQuery; import static io.trino.spi.StandardErrorCode.ALREADY_EXISTS; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/UserDatabaseKey.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/UserDatabaseKey.java deleted file mode 100644 index d25645aa5e0f..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/UserDatabaseKey.java +++ /dev/null @@ -1,78 +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 static com.google.common.base.MoreObjects.toStringHelper; -import static java.util.Objects.requireNonNull; - -@Immutable -public class UserDatabaseKey -{ - private final String user; - private final String database; - - @JsonCreator - public UserDatabaseKey(@JsonProperty("user") String user, @JsonProperty("database") String database) - { - this.user = requireNonNull(user, "user is null"); - this.database = requireNonNull(database, "database is null"); - } - - @JsonProperty - public String getUser() - { - return user; - } - - @JsonProperty - public String getDatabase() - { - return database; - } - - @Override - public boolean equals(Object o) - { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - UserDatabaseKey that = (UserDatabaseKey) o; - return Objects.equals(user, that.user) && - Objects.equals(database, that.database); - } - - @Override - public int hashCode() - { - return Objects.hash(user, database); - } - - @Override - public String toString() - { - return toStringHelper(this) - .add("principalName", user) - .add("database", database) - .toString(); - } -} 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 89343de0c71a..9ba3811bea5f 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 @@ -44,13 +44,13 @@ import io.trino.metastore.PartitionStatistics; import io.trino.metastore.PartitionWithStatistics; import io.trino.metastore.PrincipalPrivileges; +import io.trino.metastore.SchemaAlreadyExistsException; import io.trino.metastore.StatisticsUpdateMode; import io.trino.metastore.Table; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.metastore.TableInfo; import io.trino.plugin.hive.NodeVersion; import io.trino.plugin.hive.PartitionNotFoundException; -import io.trino.plugin.hive.SchemaAlreadyExistsException; -import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.TableType; import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig.VersionCompatibility; import io.trino.spi.TrinoException; @@ -83,6 +83,7 @@ import java.util.OptionalLong; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkArgument; @@ -92,8 +93,9 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.hash.Hashing.sha256; import static io.trino.metastore.HivePrivilegeInfo.HivePrivilege.OWNERSHIP; -import static io.trino.metastore.Partition.toPartitionValues; -import static io.trino.metastore.Partition.unescapePathName; +import static io.trino.metastore.Partitions.escapePathName; +import static io.trino.metastore.Partitions.toPartitionValues; +import static io.trino.metastore.Partitions.unescapePathName; import static io.trino.metastore.Table.TABLE_COMMENT; import static io.trino.plugin.hive.HiveErrorCode.HIVE_CONCURRENT_MODIFICATION_DETECTED; import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; @@ -115,7 +117,6 @@ import static io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig.VersionCompatibility.UNSAFE_ASSUME_COMPATIBILITY; import static io.trino.plugin.hive.util.HiveUtil.DELTA_LAKE_PROVIDER; import static io.trino.plugin.hive.util.HiveUtil.SPARK_TABLE_PROVIDER_KEY; -import static io.trino.plugin.hive.util.HiveUtil.escapePathName; import static io.trino.plugin.hive.util.HiveUtil.escapeSchemaName; import static io.trino.plugin.hive.util.HiveUtil.escapeTableName; import static io.trino.plugin.hive.util.HiveUtil.isIcebergTable; @@ -177,7 +178,7 @@ public FileHiveMetastore(NodeVersion nodeVersion, TrinoFileSystemFactory fileSys listTablesCache = EvictableCacheBuilder.newBuilder() .expireAfterWrite(10, SECONDS) - .build(CacheLoader.from(this::doListAllTables)); + .build(CacheLoader.from(databaseName -> doListAllTables(databaseName, _ -> true))); } @Override @@ -532,7 +533,16 @@ private List listAllTables(String databaseName) return listTablesCache.getUnchecked(databaseName); } - private synchronized List doListAllTables(String databaseName) + @Override + public synchronized List getTableNamesWithParameters(String databaseName, String parameterKey, ImmutableSet parameterValues) + { + requireNonNull(parameterKey, "parameterKey is null"); + return doListAllTables(databaseName, table -> parameterValues.contains(table.getParameters().get(parameterKey))).stream() + .map(tableInfo -> tableInfo.tableName().getTableName()) + .collect(toImmutableList()); + } + + private synchronized List doListAllTables(String databaseName, Predicate tableMetadataPredicate) { requireNonNull(databaseName, "databaseName is null"); @@ -557,7 +567,8 @@ private synchronized List doListAllTables(String databaseName) Location schemaFileLocation = subdirectory.appendPath(TRINO_SCHEMA_FILE_NAME_SUFFIX); readFile("table schema", schemaFileLocation, tableCodec).ifPresent(tableMetadata -> { checkVersion(tableMetadata.getWriterVersion()); - if (hideDeltaLakeTables && DELTA_LAKE_PROVIDER.equals(tableMetadata.getParameters().get(SPARK_TABLE_PROVIDER_KEY))) { + if ((hideDeltaLakeTables && DELTA_LAKE_PROVIDER.equals(tableMetadata.getParameters().get(SPARK_TABLE_PROVIDER_KEY))) + || !tableMetadataPredicate.test(tableMetadata)) { return; } tables.add(new TableInfo( diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastoreFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastoreFactory.java index 28141fb1c668..6d1a9101c7a4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastoreFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastoreFactory.java @@ -17,10 +17,10 @@ import io.opentelemetry.api.trace.Tracer; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.tracing.TracingHiveMetastore; import io.trino.plugin.hive.HideDeltaLakeTables; import io.trino.plugin.hive.NodeVersion; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.security.ConnectorIdentity; import java.util.Optional; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileMetastoreModule.java index 71b0a7450679..2490ec55ea4c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileMetastoreModule.java @@ -17,9 +17,9 @@ import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Scopes; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.plugin.hive.AllowHiveTableRename; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; import static io.airlift.configuration.ConfigBinder.configBinder; 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 59e05ac8c334..a7103e4326cc 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 @@ -35,13 +35,13 @@ import io.trino.metastore.PartitionStatistics; import io.trino.metastore.PartitionWithStatistics; import io.trino.metastore.PrincipalPrivileges; +import io.trino.metastore.SchemaAlreadyExistsException; import io.trino.metastore.StatisticsUpdateMode; import io.trino.metastore.Table; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.metastore.TableInfo; import io.trino.plugin.hive.HivePartitionManager; import io.trino.plugin.hive.PartitionNotFoundException; -import io.trino.plugin.hive.SchemaAlreadyExistsException; -import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.spi.ErrorCode; import io.trino.spi.TrinoException; import io.trino.spi.connector.SchemaNotFoundException; @@ -412,10 +412,21 @@ public void setDatabaseOwner(String databaseName, HivePrincipal principal) @Override public List getTables(String databaseName) { - return glueCache.getTables(databaseName, cacheTable -> getTablesInternal(cacheTable, databaseName)); + return glueCache.getTables(databaseName, cacheTable -> getTablesInternal(cacheTable, databaseName, _ -> true)); } - private List getTablesInternal(Consumer cacheTable, String databaseName) + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, ImmutableSet parameterValues) + { + return getTablesInternal( + _ -> {}, + databaseName, + table -> table.parameters() != null && parameterValues.contains(table.parameters().get(parameterKey))).stream() + .map(tableInfo -> tableInfo.tableName().getTableName()) + .collect(toImmutableList()); + } + + private List getTablesInternal(Consumer
cacheTable, String databaseName, Predicate filter) { try { ImmutableList glueTables = stats.getGetTables() @@ -425,6 +436,7 @@ private List getTablesInternal(Consumer
cacheTable, String dat .map(GetTablesResponse::tableList) .flatMap(List::stream)) .filter(tableVisibilityFilter) + .filter(filter) .collect(toImmutableList()); // Store only valid tables in cache diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastoreFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastoreFactory.java index 83ca8b6090c7..8b5075e606e6 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastoreFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastoreFactory.java @@ -16,8 +16,8 @@ import com.google.inject.Inject; import io.opentelemetry.api.trace.Tracer; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.tracing.TracingHiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.security.ConnectorIdentity; import java.util.Optional; 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 584ad46efe92..b4fe87a8ee7e 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 @@ -27,11 +27,11 @@ import io.airlift.units.Duration; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkTelemetry; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; +import io.trino.metastore.cache.CachingHiveMetastoreConfig; import io.trino.plugin.hive.AllowHiveTableRename; import io.trino.plugin.hive.HideDeltaLakeTables; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastoreConfig; import io.trino.spi.NodeManager; import io.trino.spi.catalog.CatalogName; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/InMemoryGlueCache.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/InMemoryGlueCache.java index fe1980b1f69f..dc627293c35e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/InMemoryGlueCache.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/InMemoryGlueCache.java @@ -26,7 +26,7 @@ import io.trino.metastore.Partition; import io.trino.metastore.Table; import io.trino.metastore.TableInfo; -import io.trino.plugin.hive.metastore.cache.ReentrantBoundedExecutor; +import io.trino.metastore.cache.ReentrantBoundedExecutor; import io.trino.spi.catalog.CatalogName; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.function.LanguageFunction; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/AwsSdkClientCoreStats.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/AwsSdkClientCoreStats.java index 8d48c88582d0..95517512bbc2 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/AwsSdkClientCoreStats.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/AwsSdkClientCoreStats.java @@ -44,6 +44,7 @@ public final class AwsSdkClientCoreStats { private final CounterStat awsRequestCount = new CounterStat(); private final CounterStat awsRetryCount = new CounterStat(); + private final CounterStat awsHttpClientRetryCount = new CounterStat(); private final CounterStat awsThrottleExceptions = new CounterStat(); private final TimeStat awsRequestTime = new TimeStat(MILLISECONDS); private final TimeStat awsClientExecuteTime = new TimeStat(MILLISECONDS); @@ -66,6 +67,13 @@ public CounterStat getAwsRetryCount() return awsRetryCount; } + @Managed + @Nested + public CounterStat getAwsHttpClientRetryCount() + { + return awsHttpClientRetryCount; + } + @Managed @Nested public CounterStat getAwsThrottleExceptions() @@ -134,12 +142,16 @@ public void collectMetrics(Request request, Response response) Number requestCounts = timingInfo.getCounter(RequestCount.name()); if (requestCounts != null) { - stats.awsRequestCount.update(requestCounts.longValue()); + long count = requestCounts.longValue(); + stats.awsRequestCount.update(count); + if (count > 1) { + stats.awsRetryCount.update(count - 1); + } } - Number retryCounts = timingInfo.getCounter(HttpClientRetryCount.name()); - if (retryCounts != null) { - stats.awsRetryCount.update(retryCounts.longValue()); + Number httpClientRetryCounts = timingInfo.getCounter(HttpClientRetryCount.name()); + if (httpClientRetryCounts != null) { + stats.awsHttpClientRetryCount.update(httpClientRetryCounts.longValue()); } Number throttleExceptions = timingInfo.getCounter(ThrottleException.name()); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/DefaultGlueColumnStatisticsProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/DefaultGlueColumnStatisticsProvider.java index 7dc27115fa8f..7fe6b886bf62 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/DefaultGlueColumnStatisticsProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/DefaultGlueColumnStatisticsProvider.java @@ -53,11 +53,11 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Sets.difference; import static io.airlift.concurrent.MoreFutures.getFutureValue; -import static io.trino.metastore.Partition.toPartitionValues; +import static io.trino.metastore.Partitions.toPartitionValues; import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_PARTITION_NOT_FOUND; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueStatConverter.fromGlueColumnStatistics; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueStatConverter.toGlueColumnStatistics; +import static io.trino.plugin.hive.metastore.glue.v1.GlueStatConverter.fromGlueColumnStatistics; +import static io.trino.plugin.hive.metastore.glue.v1.GlueStatConverter.toGlueColumnStatistics; import static java.util.concurrent.CompletableFuture.allOf; import static java.util.concurrent.CompletableFuture.runAsync; import static java.util.concurrent.CompletableFuture.supplyAsync; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/DefaultGlueMetastoreTableFilterProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/DefaultGlueMetastoreTableFilterProvider.java index a072f2682239..54f81a1eb184 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/DefaultGlueMetastoreTableFilterProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/DefaultGlueMetastoreTableFilterProvider.java @@ -20,7 +20,7 @@ import java.util.function.Predicate; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; import static io.trino.plugin.hive.util.HiveUtil.isDeltaLakeTable; import static java.util.function.Predicate.not; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueHiveMetastore.java index a988769b58ee..8e24cdfcf873 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueHiveMetastore.java @@ -85,19 +85,18 @@ import io.trino.metastore.Partition; import io.trino.metastore.PartitionStatistics; import io.trino.metastore.PartitionWithStatistics; +import io.trino.metastore.Partitions; import io.trino.metastore.PrincipalPrivileges; +import io.trino.metastore.SchemaAlreadyExistsException; import io.trino.metastore.StatisticsUpdateMode; import io.trino.metastore.Table; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.metastore.TableInfo; import io.trino.plugin.hive.PartitionNotFoundException; -import io.trino.plugin.hive.SchemaAlreadyExistsException; -import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.metastore.glue.AwsApiCallStats; import io.trino.plugin.hive.metastore.glue.GlueExpressionUtil; import io.trino.plugin.hive.metastore.glue.GlueMetastoreStats; -import io.trino.plugin.hive.metastore.glue.v1.converter.GlueInputConverter; -import io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter; -import io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.GluePartitionConverter; +import io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.GluePartitionConverter; import io.trino.spi.TrinoException; import io.trino.spi.connector.ColumnNotFoundException; import io.trino.spi.connector.SchemaNotFoundException; @@ -136,7 +135,7 @@ 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.metastore.Partition.toPartitionValues; +import static io.trino.metastore.Partitions.toPartitionValues; import static io.trino.metastore.Table.TABLE_COMMENT; import static io.trino.plugin.hive.HiveErrorCode.HIVE_FILESYSTEM_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_METADATA; @@ -149,12 +148,12 @@ import static io.trino.plugin.hive.metastore.MetastoreUtil.updateStatisticsParameters; import static io.trino.plugin.hive.metastore.MetastoreUtil.verifyCanDropColumn; import static io.trino.plugin.hive.metastore.glue.v1.AwsSdkUtil.getPaginatedResults; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueInputConverter.convertFunction; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueInputConverter.convertGlueTableToTableInput; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueInputConverter.convertPartition; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableType; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.mappedCopy; +import static io.trino.plugin.hive.metastore.glue.v1.GlueInputConverter.convertFunction; +import static io.trino.plugin.hive.metastore.glue.v1.GlueInputConverter.convertGlueTableToTableInput; +import static io.trino.plugin.hive.metastore.glue.v1.GlueInputConverter.convertPartition; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableType; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.mappedCopy; import static io.trino.plugin.hive.util.HiveUtil.escapeSchemaName; import static io.trino.spi.StandardErrorCode.ALREADY_EXISTS; import static io.trino.spi.StandardErrorCode.FUNCTION_NOT_FOUND; @@ -374,6 +373,25 @@ public List getTables(String databaseName) } } + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, ImmutableSet parameterValues) + { + try { + return getGlueTables(databaseName) + .filter(tableFilter) + .filter(table -> parameterValues.contains(getTableParameters(table).get(parameterKey))) + .map(com.amazonaws.services.glue.model.Table::getName) + .collect(toImmutableList()); + } + catch (EntityNotFoundException | AccessDeniedException e) { + // database does not exist or permission denied + return ImmutableList.of(); + } + catch (AmazonServiceException e) { + throw new TrinoException(HIVE_METASTORE_ERROR, e); + } + } + @Override public Optional
getTable(String databaseName, String tableName) { @@ -838,7 +856,7 @@ private Map> getPartitionsByNamesInternal(Table tabl List partitions = batchGetPartition(table, partitionNames); Map> partitionNameToPartitionValuesMap = partitionNames.stream() - .collect(toMap(identity(), Partition::toPartitionValues)); + .collect(toMap(identity(), Partitions::toPartitionValues)); Map, Partition> partitionValuesToPartitionMap = partitions.stream() .collect(toMap(Partition::getValues, identity())); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueHiveMetastoreFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueHiveMetastoreFactory.java index e52fdcf75c56..8d6412b08f5f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueHiveMetastoreFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueHiveMetastoreFactory.java @@ -16,8 +16,8 @@ import com.google.inject.Inject; import io.opentelemetry.api.trace.Tracer; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.tracing.TracingHiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.security.ConnectorIdentity; import java.util.Optional; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/GlueInputConverter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueInputConverter.java similarity index 95% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/GlueInputConverter.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueInputConverter.java index 1d12277e87d4..9d16be3760ce 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/GlueInputConverter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueInputConverter.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.glue.v1.converter; +package io.trino.plugin.hive.metastore.glue.v1; import com.amazonaws.services.glue.model.DatabaseInput; import com.amazonaws.services.glue.model.Order; @@ -48,9 +48,9 @@ import static io.trino.plugin.hive.metastore.MetastoreUtil.metastoreFunctionName; import static io.trino.plugin.hive.metastore.MetastoreUtil.toResourceUris; import static io.trino.plugin.hive.metastore.MetastoreUtil.updateStatisticsParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getStorageDescriptor; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableTypeNullable; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getStorageDescriptor; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableTypeNullable; public final class GlueInputConverter { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueMetastoreModule.java index 5bdd79f6ace3..1d1bbf384cf3 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueMetastoreModule.java @@ -31,9 +31,9 @@ import io.airlift.configuration.AbstractConfigurationAwareModule; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.awssdk.v1_11.AwsSdkTelemetry; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.plugin.hive.AllowHiveTableRename; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; import io.trino.plugin.hive.metastore.glue.GlueMetastoreStats; import java.lang.annotation.Annotation; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/GlueStatConverter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueStatConverter.java similarity index 99% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/GlueStatConverter.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueStatConverter.java index 13ccbb855996..fc71676c5a36 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/GlueStatConverter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueStatConverter.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.glue.v1.converter; +package io.trino.plugin.hive.metastore.glue.v1; import com.amazonaws.services.glue.model.BinaryColumnStatisticsData; import com.amazonaws.services.glue.model.BooleanColumnStatisticsData; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/GlueToTrinoConverter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueToTrinoConverter.java similarity index 99% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/GlueToTrinoConverter.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueToTrinoConverter.java index 53bed07ae493..3ab9906c170c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/GlueToTrinoConverter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/GlueToTrinoConverter.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.glue.v1.converter; +package io.trino.plugin.hive.metastore.glue.v1; import com.amazonaws.services.glue.model.SerDeInfo; import com.amazonaws.services.glue.model.StorageDescriptor; @@ -55,7 +55,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_UNSUPPORTED_FORMAT; import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; -import static io.trino.plugin.hive.metastore.glue.v1.converter.Memoizers.memoizeLast; +import static io.trino.plugin.hive.metastore.glue.v1.Memoizers.memoizeLast; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.decodeFunction; import static io.trino.plugin.hive.util.HiveUtil.isDeltaLakeTable; import static io.trino.plugin.hive.util.HiveUtil.isIcebergTable; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/Memoizers.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/Memoizers.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/Memoizers.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/Memoizers.java index 3ae763a83f4e..43d4af02a23b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/converter/Memoizers.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/v1/Memoizers.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore.glue.v1.converter; +package io.trino.plugin.hive.metastore.glue.v1; import java.util.Objects; import java.util.function.BiFunction; 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 826a6caf0c31..e66c5fdf76e1 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 @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import io.trino.hive.thrift.metastore.FieldSchema; import io.trino.metastore.AcidOperation; import io.trino.metastore.AcidTransactionOwner; @@ -29,12 +30,13 @@ import io.trino.metastore.Partition; import io.trino.metastore.PartitionStatistics; import io.trino.metastore.PartitionWithStatistics; +import io.trino.metastore.Partitions; import io.trino.metastore.PrincipalPrivileges; +import io.trino.metastore.SchemaAlreadyExistsException; import io.trino.metastore.StatisticsUpdateMode; import io.trino.metastore.Table; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.metastore.TableInfo; -import io.trino.plugin.hive.SchemaAlreadyExistsException; -import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.spi.TrinoException; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; @@ -55,7 +57,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.metastore.Table.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.metastoreFunctionName; import static io.trino.plugin.hive.metastore.MetastoreUtil.verifyCanDropColumn; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.csvSchemaFields; @@ -152,6 +153,12 @@ public List getTables(String databaseName) .collect(toImmutableList()); } + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, ImmutableSet parameterValues) + { + return delegate.getTableNamesWithParameters(databaseName, parameterKey, parameterValues); + } + @Override public void createDatabase(Database database) { @@ -381,7 +388,7 @@ public Map> getPartitionsByNames(Table table, List> partitionNameToPartitionValuesMap = partitionNames.stream() - .collect(Collectors.toMap(identity(), Partition::toPartitionValues)); + .collect(Collectors.toMap(identity(), Partitions::toPartitionValues)); Map, Partition> partitionValuesToPartitionMap = delegate.getPartitionsByNames(table.getDatabaseName(), table.getTableName(), partitionNames).stream() .map(partition -> fromMetastoreApiPartition(table, partition)) .collect(Collectors.toMap(Partition::getValues, identity())); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastoreFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastoreFactory.java index b4875165b79f..632045081656 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastoreFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastoreFactory.java @@ -16,8 +16,8 @@ import com.google.inject.Inject; import io.opentelemetry.api.trace.Tracer; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.tracing.TracingHiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.security.ConnectorIdentity; import java.util.Optional; 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 ec0d33783f82..a01a87b9e69f 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 @@ -49,6 +49,7 @@ public class DefaultThriftMetastoreClientFactory private final MetastoreSupportsDateStatistics metastoreSupportsDateStatistics = new MetastoreSupportsDateStatistics(); private final AtomicInteger chosenGetTableAlternative = new AtomicInteger(Integer.MAX_VALUE); + private final AtomicInteger chosenTableParamAlternative = new AtomicInteger(Integer.MAX_VALUE); private final AtomicInteger chosenAlterTransactionalTableAlternative = new AtomicInteger(Integer.MAX_VALUE); private final AtomicInteger chosenAlterPartitionsAlternative = new AtomicInteger(Integer.MAX_VALUE); @@ -115,6 +116,7 @@ protected ThriftMetastoreClient create(TransportSupplier transportSupplier, Stri metastoreSupportsDateStatistics, true, chosenGetTableAlternative, + chosenTableParamAlternative, chosenAlterTransactionalTableAlternative, chosenAlterPartitionsAlternative); } 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 ed57071e3389..0d9ee63183ab 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 @@ -37,6 +37,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import static java.util.Objects.requireNonNull; @@ -92,6 +93,13 @@ public List getTableMeta(String databaseName) return runWithHandle(() -> delegate.getTableMeta(databaseName)); } + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, Set parameterValues) + throws TException + { + return runWithHandle(() -> delegate.getTableNamesWithParameters(databaseName, parameterKey, parameterValues)); + } + @Override public void createDatabase(Database database) throws TException diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ForHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ForHiveMetastore.java similarity index 95% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/ForHiveMetastore.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ForHiveMetastore.java index 1b5ca893bff4..f54ed16dc51d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ForHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ForHiveMetastore.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive; +package io.trino.plugin.hive.metastore.thrift; import com.google.inject.BindingAnnotation; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/HttpThriftMetastoreClientFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/HttpThriftMetastoreClientFactory.java index 52b0df41ca1b..ddd696fa9d54 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/HttpThriftMetastoreClientFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/HttpThriftMetastoreClientFactory.java @@ -57,6 +57,7 @@ public class HttpThriftMetastoreClientFactory private final OpenTelemetry openTelemetry; private final AtomicInteger chosenGetTableAlternative = new AtomicInteger(Integer.MAX_VALUE); + private final AtomicInteger chosenGetTableParamAlternative = new AtomicInteger(Integer.MAX_VALUE); private final AtomicInteger chosenAlterTransactionalTableAlternative = new AtomicInteger(Integer.MAX_VALUE); private final AtomicInteger chosenAlterPartitionsAlternative = new AtomicInteger(Integer.MAX_VALUE); @@ -85,6 +86,7 @@ public ThriftMetastoreClient create(URI uri, Optional delegationToken) new MetastoreSupportsDateStatistics(), false, chosenGetTableAlternative, + chosenGetTableParamAlternative, chosenAlterTransactionalTableAlternative, chosenAlterPartitionsAlternative); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/KerberosHiveMetastoreAuthentication.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/KerberosHiveMetastoreAuthentication.java index 834928ea07d0..5f0da82d63f0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/KerberosHiveMetastoreAuthentication.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/KerberosHiveMetastoreAuthentication.java @@ -19,7 +19,6 @@ import io.airlift.slice.SliceInput; import io.airlift.slice.Slices; import io.trino.plugin.base.authentication.CachingKerberosAuthentication; -import io.trino.plugin.hive.ForHiveMetastore; import org.apache.thrift.transport.TSaslClientTransport; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/RetryDriver.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/RetryDriver.java similarity index 99% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/RetryDriver.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/RetryDriver.java index 10f31de8482d..7d1aecdd9924 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/RetryDriver.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/RetryDriver.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.util; +package io.trino.plugin.hive.metastore.thrift; import com.google.common.collect.ImmutableList; import io.airlift.log.Logger; 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 a7908d8e7de0..41fdbffd42ad 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 @@ -69,11 +69,10 @@ import io.trino.metastore.HiveType; import io.trino.metastore.PartitionStatistics; import io.trino.metastore.PartitionWithStatistics; +import io.trino.metastore.SchemaAlreadyExistsException; import io.trino.metastore.StatisticsUpdateMode; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.plugin.hive.PartitionNotFoundException; -import io.trino.plugin.hive.SchemaAlreadyExistsException; -import io.trino.plugin.hive.TableAlreadyExistsException; -import io.trino.plugin.hive.util.RetryDriver; import io.trino.spi.TrinoException; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; @@ -268,6 +267,30 @@ public List getTables(String databaseName) } } + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, Set parameterValues) + { + try { + return retry() + .stopOn(NoSuchObjectException.class) + .stopOnIllegalExceptions() + .run("getTableNamesWithParameters", () -> { + try (ThriftMetastoreClient client = createMetastoreClient()) { + return client.getTableNamesWithParameters(databaseName, parameterKey, parameterValues); + } + }); + } + catch (NoSuchObjectException e) { + return ImmutableList.of(); + } + catch (TException e) { + throw new TrinoException(HIVE_METASTORE_ERROR, e); + } + catch (Exception e) { + throw propagate(e); + } + } + @Override public Optional
getTable(String databaseName, String tableName) { 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 acf253025ef5..97c7a5fb3ead 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 @@ -78,8 +78,10 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; +import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Throwables.throwIfInstanceOf; @@ -90,6 +92,7 @@ import static com.google.common.reflect.Reflection.newProxy; import static io.trino.hive.thrift.metastore.GrantRevokeType.GRANT; import static io.trino.hive.thrift.metastore.GrantRevokeType.REVOKE; +import static io.trino.hive.thrift.metastore.hive_metastoreConstants.HIVE_FILTER_FIELD_PARAMS; import static io.trino.metastore.TableInfo.PRESTO_VIEW_COMMENT; import static io.trino.plugin.hive.TableType.VIRTUAL_VIEW; import static io.trino.plugin.hive.metastore.thrift.MetastoreSupportsDateStatistics.DateStatisticsSupport.NOT_SUPPORTED; @@ -99,6 +102,7 @@ import static io.trino.plugin.hive.metastore.thrift.TxnUtils.createValidTxnWriteIdList; import static java.lang.String.format; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; import static org.apache.thrift.TApplicationException.UNKNOWN_METHOD; public class ThriftHiveMetastoreClient @@ -109,6 +113,9 @@ public class ThriftHiveMetastoreClient private static final String CATALOG_DB_SEPARATOR = "#"; private static final String DB_EMPTY_MARKER = "!"; + private static final Pattern TABLE_PARAMETER_SAFE_KEY_PATTERN = Pattern.compile("^[a-zA-Z_]+$"); + private static final Pattern TABLE_PARAMETER_SAFE_VALUE_PATTERN = Pattern.compile("^[a-zA-Z0-9\\s]*$"); + private final TransportSupplier transportSupplier; private TTransport transport; protected ThriftHiveMetastore.Iface client; @@ -117,6 +124,7 @@ public class ThriftHiveMetastoreClient private final MetastoreSupportsDateStatistics metastoreSupportsDateStatistics; private final boolean metastoreSupportsTableMeta; private final AtomicInteger chosenGetTableAlternative; + private final AtomicInteger chosenTableParamAlternative; private final AtomicInteger chosenAlterTransactionalTableAlternative; private final AtomicInteger chosenAlterPartitionsAlternative; private final Optional catalogName; @@ -128,6 +136,7 @@ public ThriftHiveMetastoreClient( MetastoreSupportsDateStatistics metastoreSupportsDateStatistics, boolean metastoreSupportsTableMeta, AtomicInteger chosenGetTableAlternative, + AtomicInteger chosenTableParamAlternative, AtomicInteger chosenAlterTransactionalTableAlternative, AtomicInteger chosenAlterPartitionsAlternative) throws TTransportException @@ -137,6 +146,7 @@ public ThriftHiveMetastoreClient( this.metastoreSupportsDateStatistics = requireNonNull(metastoreSupportsDateStatistics, "metastoreSupportsDateStatistics is null"); this.metastoreSupportsTableMeta = metastoreSupportsTableMeta; this.chosenGetTableAlternative = requireNonNull(chosenGetTableAlternative, "chosenGetTableAlternative is null"); + this.chosenTableParamAlternative = requireNonNull(chosenTableParamAlternative, "chosenTableParamAlternative is null"); this.chosenAlterTransactionalTableAlternative = requireNonNull(chosenAlterTransactionalTableAlternative, "chosenAlterTransactionalTableAlternative is null"); this.chosenAlterPartitionsAlternative = requireNonNull(chosenAlterPartitionsAlternative, "chosenAlterPartitionsAlternative is null"); this.catalogName = requireNonNull(catalogName, "catalogName is null"); @@ -210,6 +220,41 @@ public List getTableMeta(String databaseName) return client.getTableMeta(prependCatalogToDbName(catalogName, databaseName), "*", ImmutableList.of()); } + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, Set parameterValues) + throws TException + { + checkArgument(TABLE_PARAMETER_SAFE_KEY_PATTERN.matcher(parameterKey).matches(), "Parameter key contains invalid characters: '%s'", parameterKey); + /* + * The parameter value is restricted to have only alphanumeric characters so that it's safe + * to be used against HMS. When using with a LIKE operator, the HMS may want the parameter + * value to follow a Java regex pattern or an SQL pattern. And it's hard to predict the + * HMS's behavior from outside. Also, by restricting parameter values, we avoid the problem + * of how to quote them when passing within the filter string. + */ + for (String parameterValue : parameterValues) { + checkArgument(TABLE_PARAMETER_SAFE_VALUE_PATTERN.matcher(parameterValue).matches(), "Parameter value contains invalid characters: '%s'", parameterValue); + } + /* + * Thrift call `get_table_names_by_filter` may be translated by Metastore to an SQL query against Metastore database. + * Hive 2.3 on some databases uses CLOB for table parameter value column and some databases disallow `=` predicate over + * CLOB values. At the same time, they allow `LIKE` predicates over them. + */ + String filterWithEquals = parameterValues.stream() + .map(parameterValue -> HIVE_FILTER_FIELD_PARAMS + parameterKey + " = \"" + parameterValue + "\"") + .collect(joining(" or ")); + + String filterWithLike = parameterValues.stream() + .map(parameterValue -> HIVE_FILTER_FIELD_PARAMS + parameterKey + " LIKE \"" + parameterValue + "\"") + .collect(joining(" or ")); + + return alternativeCall( + ThriftHiveMetastoreClient::defaultIsValidExceptionalResponse, + chosenTableParamAlternative, + () -> client.getTableNamesByFilter(databaseName, filterWithEquals, (short) -1), + () -> client.getTableNamesByFilter(databaseName, filterWithLike, (short) -1)); + } + @Override public void createDatabase(Database database) throws TException diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHttpMetastoreFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHttpMetastoreFactory.java index bfc8e8c7820f..26e13e5abae0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHttpMetastoreFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHttpMetastoreFactory.java @@ -16,7 +16,6 @@ import com.google.inject.Inject; import io.airlift.units.Duration; import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.plugin.hive.util.RetryDriver; import io.trino.spi.security.ConnectorIdentity; import org.weakref.jmx.Flatten; import org.weakref.jmx.Managed; 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 ebcdbf5359d4..6f9cf7a04869 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 @@ -65,6 +65,8 @@ public sealed interface ThriftMetastore List getTables(String databaseName); + List getTableNamesWithParameters(String databaseName, String parameterKey, Set parameterValues); + Optional getDatabase(String databaseName); void addPartitions(String databaseName, String tableName, List partitions); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreAuthenticationModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreAuthenticationModule.java index 066c6628e01f..b244f7f0cdcb 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreAuthenticationModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreAuthenticationModule.java @@ -21,7 +21,6 @@ import io.trino.plugin.base.authentication.CachingKerberosAuthentication; import io.trino.plugin.base.authentication.KerberosAuthentication; import io.trino.plugin.base.authentication.KerberosConfiguration; -import io.trino.plugin.hive.ForHiveMetastore; import static com.google.inject.Scopes.SINGLETON; import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; 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 b9c39b54a333..7fe9ef73f04f 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 @@ -37,6 +37,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; public interface ThriftMetastoreClient extends Closeable @@ -53,6 +54,9 @@ Database getDatabase(String databaseName) List getTableMeta(String databaseName) throws TException; + List getTableNamesWithParameters(String databaseName, String parameterKey, Set parameterValues) + throws TException; + void createDatabase(Database database) throws TException; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreConfig.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreConfig.java index b4511e45c17b..ae4c96b10b83 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreConfig.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreConfig.java @@ -22,7 +22,6 @@ import io.airlift.configuration.validation.FileExists; import io.airlift.units.Duration; import io.airlift.units.MinDuration; -import io.trino.plugin.hive.util.RetryDriver; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreModule.java index f3b3c488260d..4bef109c2f4d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreModule.java @@ -21,11 +21,10 @@ import com.google.inject.TypeLiteral; import com.google.inject.multibindings.OptionalBinder; import io.airlift.configuration.AbstractConfigurationAwareModule; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.plugin.base.security.UserNameProvider; import io.trino.plugin.hive.AllowHiveTableRename; -import io.trino.plugin.hive.ForHiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; import java.util.concurrent.ExecutorService; 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 7fd79ede9317..1e377956c03d 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 @@ -974,4 +974,13 @@ public static DataOperationType toDataOperationType(AcidOperation acidOperation) default -> throw new IllegalStateException("No metastore operation for ACID operation " + acidOperation); }; } + + public static boolean isAvroTableWithSchemaSet(Table table) + { + return AVRO.getSerde().equals(table.getStorage().getStorageFormat().getSerDeNullable()) && + ((table.getParameters().get(AVRO_SCHEMA_URL_KEY) != null || + (table.getStorage().getSerdeParameters().get(AVRO_SCHEMA_URL_KEY) != null)) || + (table.getParameters().get(AVRO_SCHEMA_LITERAL_KEY) != null || + (table.getStorage().getSerdeParameters().get(AVRO_SCHEMA_LITERAL_KEY) != null))); + } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/TokenFetchingMetastoreClientFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/TokenFetchingMetastoreClientFactory.java index ab95396c19e1..3f68f3499633 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/TokenFetchingMetastoreClientFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/TokenFetchingMetastoreClientFactory.java @@ -23,7 +23,6 @@ import dev.failsafe.function.CheckedSupplier; import io.trino.cache.NonEvictableLoadingCache; import io.trino.plugin.base.security.UserNameProvider; -import io.trino.plugin.hive.ForHiveMetastore; import io.trino.spi.TrinoException; import io.trino.spi.security.ConnectorIdentity; import org.apache.thrift.TException; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/UgiBasedMetastoreClientFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/UgiBasedMetastoreClientFactory.java index f69bd4575a94..5408300b951d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/UgiBasedMetastoreClientFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/UgiBasedMetastoreClientFactory.java @@ -15,7 +15,6 @@ import com.google.inject.Inject; import io.trino.plugin.base.security.UserNameProvider; -import io.trino.plugin.hive.ForHiveMetastore; import io.trino.spi.security.ConnectorIdentity; import org.apache.thrift.TException; 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 dc072f0967a0..f364a9e18739 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 @@ -253,7 +253,7 @@ public static ReaderPageSource createPageSource( start, length, dataSource, - parquetMetadata.getBlocks(), + parquetMetadata, parquetTupleDomains, parquetPredicates, descriptorsByPath, 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 a8f20e585d7d..21d2792ce39f 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 @@ -23,7 +23,6 @@ import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HiveInsertTableHandle; -import io.trino.plugin.hive.HiveTableHandle; import io.trino.plugin.hive.LocationService; import io.trino.plugin.hive.LocationService.WriteInfo; import io.trino.plugin.hive.PartitionUpdate; @@ -34,6 +33,7 @@ import io.trino.spi.classloader.ThreadContextClassLoader; import io.trino.spi.connector.ConnectorAccessControl; import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorTableHandle; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.procedure.Procedure; import io.trino.spi.procedure.Procedure.Argument; @@ -45,8 +45,8 @@ import java.util.Optional; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.metastore.Partitions.makePartName; import static io.trino.plugin.base.util.Procedures.checkProcedureArgument; -import static io.trino.plugin.hive.util.HiveUtil.makePartName; import static io.trino.spi.StandardErrorCode.ALREADY_EXISTS; import static io.trino.spi.StandardErrorCode.INVALID_PROCEDURE_ARGUMENT; import static io.trino.spi.connector.RetryMode.NO_RETRIES; @@ -112,14 +112,16 @@ private void doCreateEmptyPartition(ConnectorSession session, ConnectorAccessCon TransactionalMetadata hiveMetadata = hiveMetadataFactory.create(session.getIdentity(), true); hiveMetadata.beginQuery(session); try (UncheckedCloseable ignore = () -> hiveMetadata.cleanupQuery(session)) { - HiveTableHandle tableHandle = (HiveTableHandle) hiveMetadata.getTableHandle(session, new SchemaTableName(schemaName, tableName), Optional.empty(), Optional.empty()); + ConnectorTableHandle tableHandle = hiveMetadata.getTableHandle(session, new SchemaTableName(schemaName, tableName), Optional.empty(), Optional.empty()); 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() + List actualPartitionColumnNames = hiveMetadata.getColumnHandles(session, tableHandle).values().stream() + .map(HiveColumnHandle.class::cast) + .filter(HiveColumnHandle::isPartitionKey) .map(HiveColumnHandle::getName) .collect(toImmutableList()); 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 e084fac9a238..ad85ca0018c8 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 @@ -21,7 +21,6 @@ import io.trino.metastore.PartitionStatistics; import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.hive.HiveColumnHandle; -import io.trino.plugin.hive.HiveTableHandle; import io.trino.plugin.hive.TransactionalMetadata; import io.trino.plugin.hive.TransactionalMetadataFactory; import io.trino.spi.TrinoException; @@ -29,6 +28,7 @@ import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ConnectorAccessControl; import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorTableHandle; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; import io.trino.spi.predicate.TupleDomain; @@ -43,9 +43,9 @@ import java.util.OptionalLong; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.metastore.Partitions.makePartName; import static io.trino.metastore.StatisticsUpdateMode.CLEAR_ALL; import static io.trino.plugin.base.util.Procedures.checkProcedureArgument; -import static io.trino.plugin.hive.util.HiveUtil.makePartName; import static io.trino.spi.StandardErrorCode.INVALID_PROCEDURE_ARGUMENT; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; @@ -107,12 +107,13 @@ private void doDropStats(ConnectorSession session, ConnectorAccessControl access TransactionalMetadata hiveMetadata = hiveMetadataFactory.create(session.getIdentity(), true); hiveMetadata.beginQuery(session); try (UncheckedCloseable ignore = () -> hiveMetadata.cleanupQuery(session)) { - HiveTableHandle handle = (HiveTableHandle) hiveMetadata.getTableHandle(session, new SchemaTableName(schema, table), Optional.empty(), Optional.empty()); + SchemaTableName schemaTableName = new SchemaTableName(schema, table); + ConnectorTableHandle handle = hiveMetadata.getTableHandle(session, schemaTableName, Optional.empty(), Optional.empty()); if (handle == null) { - throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, format("Table '%s' does not exist", new SchemaTableName(schema, table))); + throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, format("Table '%s' does not exist", schemaTableName)); } - accessControl.checkCanInsertIntoTable(null, new SchemaTableName(schema, table)); + accessControl.checkCanInsertIntoTable(null, schemaTableName); Map columns = hiveMetadata.getColumnHandles(session, handle); List partitionColumns = columns.values().stream() @@ -131,7 +132,7 @@ private void doDropStats(ConnectorSession session, ConnectorAccessControl access partitionStringValues.forEach(values -> metastore.updatePartitionStatistics( metastore.getTable(schema, table) - .orElseThrow(() -> new TableNotFoundException(new SchemaTableName(schema, table))), + .orElseThrow(() -> new TableNotFoundException(schemaTableName)), CLEAR_ALL, ImmutableMap.of( makePartName(partitionColumns, values), @@ -150,10 +151,10 @@ private void doDropStats(ConnectorSession session, ConnectorAccessControl access } else { // the table is partitioned; remove stats for every partition - hiveMetadata.getMetastore().getPartitionNamesByFilter(handle.getSchemaName(), handle.getTableName(), partitionColumns, TupleDomain.all()) + hiveMetadata.getMetastore().getPartitionNamesByFilter(schemaTableName.getSchemaName(), schemaTableName.getTableName(), partitionColumns, TupleDomain.all()) .ifPresent(partitions -> partitions.forEach(partitionName -> metastore.updatePartitionStatistics( metastore.getTable(schema, table) - .orElseThrow(() -> new TableNotFoundException(new SchemaTableName(schema, table))), + .orElseThrow(() -> new TableNotFoundException(schemaTableName)), CLEAR_ALL, ImmutableMap.of( partitionName, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/FlushMetadataCacheProcedure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/FlushMetadataCacheProcedure.java index 78a7e63931f1..fffea2dcd713 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/FlushMetadataCacheProcedure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/FlushMetadataCacheProcedure.java @@ -18,11 +18,11 @@ import com.google.inject.Provider; import io.trino.metastore.Column; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.Table; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.HiveErrorCode; import io.trino.plugin.hive.fs.DirectoryLister; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.glue.GlueCache; import io.trino.plugin.hive.metastore.glue.PartitionName; import io.trino.spi.StandardErrorCode; @@ -41,7 +41,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.util.HiveUtil.makePartName; +import static io.trino.metastore.Partitions.makePartName; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; import static java.lang.invoke.MethodHandles.lookup; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/HiveProcedureModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/HiveProcedureModule.java index 465d4d70ebe0..1f7cb0bd32dc 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/HiveProcedureModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/HiveProcedureModule.java @@ -17,10 +17,13 @@ import com.google.inject.Module; import com.google.inject.Scopes; import com.google.inject.multibindings.Multibinder; +import io.trino.plugin.hive.fs.DirectoryLister; +import io.trino.plugin.hive.metastore.glue.GlueCache; import io.trino.spi.connector.TableProcedureMetadata; import io.trino.spi.procedure.Procedure; import static com.google.inject.multibindings.Multibinder.newSetBinder; +import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; public class HiveProcedureModule implements Module @@ -34,8 +37,12 @@ public void configure(Binder binder) procedures.addBinding().toProvider(UnregisterPartitionProcedure.class).in(Scopes.SINGLETON); procedures.addBinding().toProvider(SyncPartitionMetadataProcedure.class).in(Scopes.SINGLETON); procedures.addBinding().toProvider(DropStatsProcedure.class).in(Scopes.SINGLETON); + procedures.addBinding().toProvider(FlushMetadataCacheProcedure.class).in(Scopes.SINGLETON); Multibinder tableProcedures = newSetBinder(binder, TableProcedureMetadata.class); tableProcedures.addBinding().toProvider(OptimizeTableProcedure.class).in(Scopes.SINGLETON); + + newOptionalBinder(binder, GlueCache.class); + newOptionalBinder(binder, DirectoryLister.class); } } 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 ff0ecee88e85..dbf699288e1f 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 @@ -42,12 +42,12 @@ import java.util.List; import java.util.Optional; +import static io.trino.metastore.Partitions.makePartName; 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.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; import static io.trino.spi.StandardErrorCode.ALREADY_EXISTS; import static io.trino.spi.StandardErrorCode.INVALID_PROCEDURE_ARGUMENT; import static io.trino.spi.StandardErrorCode.PERMISSION_DENIED; 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 111c4e57f0f4..8ef9d3a5de69 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 @@ -34,10 +34,10 @@ import java.lang.invoke.MethodHandle; import java.util.List; +import static io.trino.metastore.Partitions.makePartName; import static io.trino.plugin.base.util.Procedures.checkProcedureArgument; 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; import static io.trino.spi.StandardErrorCode.NOT_FOUND; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjection.java index b6c22d7d8143..4f29ed1d63eb 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjection.java @@ -33,9 +33,9 @@ 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.metastore.Partition.toPartitionValues; +import static io.trino.metastore.Partitions.escapePathName; +import static io.trino.metastore.Partitions.toPartitionValues; import static io.trino.plugin.hive.projection.InvalidProjectionException.invalidProjectionMessage; -import static io.trino.plugin.hive.util.HiveUtil.escapePathName; 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/util/HiveUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java index a4cf437aa3b4..cf4339f11bd2 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 @@ -60,7 +60,6 @@ import org.joda.time.format.DateTimePrinter; import java.math.BigDecimal; -import java.util.HexFormat; import java.util.List; import java.util.Map; import java.util.Optional; @@ -78,6 +77,8 @@ import static io.trino.hive.formats.HiveClassNames.HUDI_REALTIME_INPUT_FORMAT; import static io.trino.hive.thrift.metastore.hive_metastoreConstants.FILE_INPUT_FORMAT; import static io.trino.metastore.HiveType.toHiveTypes; +import static io.trino.metastore.Partitions.HIVE_DEFAULT_DYNAMIC_PARTITION; +import static io.trino.metastore.Partitions.escapePathName; import static io.trino.metastore.SortingColumn.Order.ASCENDING; import static io.trino.metastore.SortingColumn.Order.DESCENDING; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; @@ -101,7 +102,6 @@ import static io.trino.plugin.hive.HiveMetadata.PARQUET_BLOOM_FILTER_COLUMNS_KEY; import static io.trino.plugin.hive.HiveMetadata.SKIP_FOOTER_COUNT_KEY; import static io.trino.plugin.hive.HiveMetadata.SKIP_HEADER_COUNT_KEY; -import static io.trino.plugin.hive.HivePartitionKey.HIVE_DEFAULT_DYNAMIC_PARTITION; 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.projection.PartitionProjectionProperties.getPartitionProjectionTrinoColumnProperties; @@ -149,8 +149,6 @@ public final class HiveUtil public static final String ICEBERG_TABLE_TYPE_NAME = "table_type"; public static final String ICEBERG_TABLE_TYPE_VALUE = "iceberg"; - private static final HexFormat HEX_UPPER_FORMAT = HexFormat.of().withUpperCase(); - private static final LocalDateTime EPOCH_DAY = new LocalDateTime(1970, 1, 1, 0, 0); private static final DateTimeFormatter HIVE_DATE_PARSER; private static final DateTimeFormatter HIVE_TIMESTAMP_PARSER; @@ -159,10 +157,6 @@ public final class HiveUtil private static final Splitter COLUMN_NAMES_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); - private static final CharMatcher PATH_CHAR_TO_ESCAPE = CharMatcher.inRange((char) 0, (char) 31) - .or(CharMatcher.anyOf("\"#%'*/:=?\\\u007F{[]^")) - .precomputed(); - private static final CharMatcher DOT_MATCHER = CharMatcher.is('.'); public static String splitError(Throwable t, Location location, long start, long length) @@ -879,59 +873,4 @@ public static String escapeTableName(String tableName) } return escapePathName(tableName); } - - // copy of org.apache.hadoop.hive.common.FileUtils#escapePathName - public static String escapePathName(String path) - { - if (isNullOrEmpty(path)) { - return HIVE_DEFAULT_DYNAMIC_PARTITION; - } - - // Fast-path detection, no escaping and therefore no copying necessary - int escapeAtIndex = PATH_CHAR_TO_ESCAPE.indexIn(path); - if (escapeAtIndex < 0) { - return path; - } - - // slow path, escape beyond the first required escape character into a new string - StringBuilder sb = new StringBuilder(); - int fromIndex = 0; - while (escapeAtIndex >= 0 && escapeAtIndex < path.length()) { - // preceding characters without escaping needed - if (escapeAtIndex > fromIndex) { - sb.append(path, fromIndex, escapeAtIndex); - } - // escape single character - char c = path.charAt(escapeAtIndex); - sb.append('%').append(HEX_UPPER_FORMAT.toHighHexDigit(c)).append(HEX_UPPER_FORMAT.toLowHexDigit(c)); - // find next character to escape - fromIndex = escapeAtIndex + 1; - if (fromIndex < path.length()) { - escapeAtIndex = PATH_CHAR_TO_ESCAPE.indexIn(path, fromIndex); - } - else { - escapeAtIndex = -1; - } - } - // trailing characters without escaping needed - if (fromIndex < path.length()) { - sb.append(path, fromIndex, path.length()); - } - return sb.toString(); - } - - // copy of org.apache.hadoop.hive.common.FileUtils#makePartName - public static String makePartName(List columns, List values) - { - StringBuilder name = new StringBuilder(); - for (int i = 0; i < columns.size(); i++) { - if (i > 0) { - name.append('/'); - } - name.append(escapePathName(columns.get(i).toLowerCase(ENGLISH))); - name.append('='); - name.append(escapePathName(values.get(i))); - } - return name.toString(); - } } 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 8744cc44283c..fc6cf307e54a 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 @@ -54,10 +54,10 @@ import java.util.Optional; import static com.google.common.io.BaseEncoding.base16; +import static io.trino.metastore.Partitions.HIVE_DEFAULT_DYNAMIC_PARTITION; 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; -import static io.trino.plugin.hive.HivePartitionKey.HIVE_DEFAULT_DYNAMIC_PARTITION; import static io.trino.plugin.hive.TableType.MANAGED_TABLE; import static io.trino.plugin.hive.TableType.MATERIALIZED_VIEW; import static io.trino.plugin.hive.metastore.MetastoreUtil.getProtectMode; 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 f182f68d5a36..765e7625827c 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 @@ -32,11 +32,11 @@ import io.trino.metadata.TableMetadata; import io.trino.metastore.Column; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.HiveType; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.Storage; import io.trino.metastore.Table; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.connector.CatalogSchemaTableName; import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.connector.Constraint; @@ -268,7 +268,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) @Override public void verifySupportsUpdateDeclaration() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_row_update", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_row_update", "AS SELECT * FROM nation")) { assertQueryFails("UPDATE " + table.getName() + " SET nationkey = 100 WHERE regionkey = 2", MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } } @@ -277,7 +277,7 @@ public void verifySupportsUpdateDeclaration() @Override public void verifySupportsRowLevelUpdateDeclaration() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_supports_update", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_supports_update", "AS SELECT * FROM nation")) { assertQueryFails("UPDATE " + table.getName() + " SET nationkey = nationkey * 100 WHERE regionkey = 2", MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } } @@ -4403,8 +4403,7 @@ public void testShowCreateTable() @Test public void testShowCreateTableWithColumnProperties() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_show_create_table_with_column_properties", "(a INT, b INT WITH (partition_projection_type = 'INTEGER', partition_projection_range = ARRAY['0', '10'])) " + "WITH (" + @@ -8439,8 +8438,7 @@ public void testWriteInvalidPrecisionTimestamp() @Test public void testCoercingVarchar0ToVarchar1() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_coercion_create_table_varchar", "(var_column_0 varchar(0), var_column_1 varchar(1), var_column_10 varchar(10))")) { assertThat(getColumnType(testTable.getName(), "var_column_0")).isEqualTo("varchar(1)"); @@ -8452,8 +8450,7 @@ public void testCoercingVarchar0ToVarchar1() @Test public void testCoercingVarchar0ToVarchar1WithCTAS() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_coercion_ctas_varchar", "AS SELECT '' AS var_column")) { assertThat(getColumnType(testTable.getName(), "var_column")).isEqualTo("varchar(1)"); @@ -8463,8 +8460,7 @@ public void testCoercingVarchar0ToVarchar1WithCTAS() @Test public void testCoercingVarchar0ToVarchar1WithCTASNoData() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_coercion_ctas_nd_varchar", "AS SELECT '' AS var_column WITH NO DATA")) { assertThat(getColumnType(testTable.getName(), "var_column")).isEqualTo("varchar(1)"); @@ -8474,8 +8470,7 @@ public void testCoercingVarchar0ToVarchar1WithCTASNoData() @Test public void testCoercingVarchar0ToVarchar1WithAddColumn() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_coercion_add_column_varchar", "(col integer)")) { assertUpdate("ALTER TABLE " + testTable.getName() + " ADD COLUMN var_column varchar(0)"); @@ -8974,8 +8969,7 @@ public void testHiddenColumnNameConflict() private void testHiddenColumnNameConflict(String columnName) { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_hidden_column_name_conflict", format("(\"%s\" int, _bucket int, _partition int) WITH (partitioned_by = ARRAY['_partition'], bucketed_by = ARRAY['_bucket'], bucket_count = 10)", columnName))) { assertThat(query("SELECT * FROM " + table.getName())) @@ -9383,8 +9377,7 @@ public void testSelectWithShortZoneId() Resources.copy(resourceLocation, out); } - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_select_with_short_zone_id_", "(id INT, firstName VARCHAR, lastName VARCHAR) WITH (external_location = '%s')".formatted(tempDir))) { assertThat(query("SELECT * FROM %s".formatted(testTable.getName()))) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseTestHiveOnDataLake.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseTestHiveOnDataLake.java new file mode 100644 index 000000000000..59df61051c61 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseTestHiveOnDataLake.java @@ -0,0 +1,2287 @@ +/* + * 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 com.google.common.collect.ImmutableSet; +import io.airlift.units.DataSize; +import io.trino.Session; +import io.trino.metastore.Column; +import io.trino.metastore.HiveColumnStatistics; +import io.trino.metastore.HiveMetastore; +import io.trino.metastore.Partition; +import io.trino.metastore.PartitionStatistics; +import io.trino.metastore.PartitionWithStatistics; +import io.trino.metastore.Table; +import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; +import io.trino.plugin.hive.s3.S3HiveQueryRunner; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.connector.TableNotFoundException; +import io.trino.spi.predicate.NullableValue; +import io.trino.spi.predicate.TupleDomain; +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.QueryRunner; +import io.trino.testing.minio.MinioClient; +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.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.temporal.TemporalUnit; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TimeZone; +import java.util.stream.Collectors; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.airlift.slice.Slices.utf8Slice; +import static io.airlift.units.DataSize.Unit.MEGABYTE; +import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; +import static io.trino.plugin.hive.metastore.MetastoreUtil.getHiveBasicStatistics; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static io.trino.testing.MaterializedResult.resultBuilder; +import static io.trino.testing.TestingNames.randomNameSuffix; +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.util.regex.Pattern.quote; +import static java.util.stream.Collectors.joining; +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.TestInstance.Lifecycle.PER_CLASS; + +@TestInstance(PER_CLASS) +abstract class BaseTestHiveOnDataLake + extends AbstractTestQueryFramework +{ + private static final String HIVE_TEST_SCHEMA = "hive_datalake"; + private static final DataSize HIVE_S3_STREAMING_PART_SIZE = DataSize.of(5, MEGABYTE); + + private final HiveMinioDataLake hiveMinioDataLake; + private final String bucketName; + + private HiveMetastore metastoreClient; + + public BaseTestHiveOnDataLake(String bucketName, HiveMinioDataLake hiveMinioDataLake) + { + this.bucketName = bucketName; + this.hiveMinioDataLake = hiveMinioDataLake; + } + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + this.hiveMinioDataLake.start(); + this.metastoreClient = new BridgingHiveMetastore( + testingThriftHiveMetastoreBuilder() + .metastoreClient(hiveMinioDataLake.getHiveMetastoreEndpoint()) + .build(this::closeAfterClass)); + return S3HiveQueryRunner.builder(hiveMinioDataLake) + .addExtraProperty("sql.path", "hive.functions") + .addExtraProperty("sql.default-function-catalog", "hive") + .addExtraProperty("sql.default-function-schema", "functions") + .setHiveProperties( + ImmutableMap.builder() + .put("hive.insert-existing-partitions-behavior", "OVERWRITE") + .put("hive.non-managed-table-writes-enabled", "true") + // Below are required to enable caching on metastore + .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("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()) + .build(); + } + + @BeforeAll + public void setUp() + { + computeActual(format( + "CREATE SCHEMA hive.%1$s WITH (location='s3a://%2$s/%1$s')", + HIVE_TEST_SCHEMA, + bucketName)); + computeActual("CREATE SCHEMA hive.functions"); + } + + @AfterAll + public void destroy() + throws Exception + { + hiveMinioDataLake.close(); + } + + @Test + public void testInsertOverwriteInTransaction() + { + String testTable = getFullyQualifiedTestTableName(); + computeActual(getCreateTableStatement(testTable, "partitioned_by=ARRAY['regionkey']")); + assertThatThrownBy( + () -> newTransaction() + .execute(getSession(), session -> { + getQueryRunner().execute(session, createInsertAsSelectFromTpchStatement(testTable)); + })) + .hasMessage("Overwriting existing partition in non auto commit context doesn't support DIRECT_TO_TARGET_EXISTING_DIRECTORY write mode"); + computeActual(format("DROP TABLE %s", testTable)); + } + + @Test + public void testInsertOverwriteNonPartitionedTable() + { + String testTable = getFullyQualifiedTestTableName(); + computeActual(getCreateTableStatement(testTable)); + assertInsertFailure( + testTable, + "Overwriting unpartitioned table not supported when writing directly to target directory"); + computeActual(format("DROP TABLE %s", testTable)); + } + + @Test + public void testInsertOverwriteNonPartitionedBucketedTable() + { + String testTable = getFullyQualifiedTestTableName(); + computeActual(getCreateTableStatement( + testTable, + "bucketed_by = ARRAY['nationkey']", + "bucket_count = 3")); + assertInsertFailure( + testTable, + "Overwriting unpartitioned table not supported when writing directly to target directory"); + computeActual(format("DROP TABLE %s", testTable)); + } + + @Test + public void testInsertOverwritePartitionedTable() + { + String testTable = getFullyQualifiedTestTableName(); + computeActual(getCreateTableStatement( + testTable, + "partitioned_by=ARRAY['regionkey']")); + copyTpchNationToTable(testTable); + assertOverwritePartition(testTable); + } + + @Test + public void testInsertOverwritePartitionedAndBucketedTable() + { + String testTable = getFullyQualifiedTestTableName(); + computeActual(getCreateTableStatement( + testTable, + "partitioned_by=ARRAY['regionkey']", + "bucketed_by = ARRAY['nationkey']", + "bucket_count = 3")); + copyTpchNationToTable(testTable); + assertOverwritePartition(testTable); + } + + @Test + public void testInsertOverwritePartitionedAndBucketedExternalTable() + { + String testTable = getFullyQualifiedTestTableName(); + // Store table data in data lake bucket + computeActual(getCreateTableStatement( + testTable, + "partitioned_by=ARRAY['regionkey']", + "bucketed_by = ARRAY['nationkey']", + "bucket_count = 3")); + copyTpchNationToTable(testTable); + + // Map this table as external table + String externalTableName = testTable + "_ext"; + computeActual(getCreateTableStatement( + externalTableName, + "partitioned_by=ARRAY['regionkey']", + "bucketed_by = ARRAY['nationkey']", + "bucket_count = 3", + format("external_location = 's3a://%s/%s/%s/'", this.bucketName, HIVE_TEST_SCHEMA, testTable))); + copyTpchNationToTable(testTable); + assertOverwritePartition(externalTableName); + } + + @Test + public void testSyncPartitionOnBucketRoot() + { + String tableName = "test_sync_partition_on_bucket_root_" + randomNameSuffix(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + hiveMinioDataLake.getMinioClient().putObject( + bucketName, + "hello\u0001world\nbye\u0001world".getBytes(UTF_8), + "part_key=part_val/data.txt"); + + assertUpdate("CREATE TABLE " + fullyQualifiedTestTableName + "(" + + " a varchar," + + " b varchar," + + " part_key varchar)" + + "WITH (" + + " external_location='s3://" + bucketName + "/'," + + " partitioned_by=ARRAY['part_key']," + + " format='TEXTFILE'" + + ")"); + + getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'ADD')"); + + assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, "VALUES ('hello', 'world', 'part_val'), ('bye', 'world', 'part_val')"); + + assertUpdate("DROP TABLE " + fullyQualifiedTestTableName); + } + + @Test + public void testSyncPartitionCaseSensitivePathVariation() + { + String tableName = "test_sync_partition_case_variation_" + randomNameSuffix(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + String tableLocation = format("s3://%s/%s/%s/", bucketName, HIVE_TEST_SCHEMA, tableName); + + hiveMinioDataLake.getMinioClient().putObject( + bucketName, + "Trino\u0001rocks".getBytes(UTF_8), + HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=part_val/data.txt"); + + assertUpdate("CREATE TABLE " + fullyQualifiedTestTableName + "(" + + " a varchar," + + " b varchar," + + " part_key varchar)" + + "WITH (" + + " external_location='" + tableLocation + "'," + + " partitioned_by=ARRAY['part_key']," + + " format='TEXTFILE'" + + ")"); + + getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'ADD')"); + assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, "VALUES ('Trino', 'rocks', 'part_val')"); + + // Move the data to a location where the partition path differs only in case + hiveMinioDataLake.getMinioClient().removeObject(bucketName, HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=part_val/data.txt"); + hiveMinioDataLake.getMinioClient().putObject( + bucketName, + "Trino\u0001rocks".getBytes(UTF_8), + HIVE_TEST_SCHEMA + "/" + tableName + "/PART_KEY=part_val/data.txt"); + + getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'FULL', case_sensitive => false)"); + assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, "VALUES ('Trino', 'rocks', 'part_val')"); + + // Verify that syncing again the partition metadata has no negative effect (e.g. drop the partition) + getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'FULL', case_sensitive => false)"); + assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, "VALUES ('Trino', 'rocks', 'part_val')"); + + assertUpdate("DROP TABLE " + fullyQualifiedTestTableName); + } + + @Test + public void testSyncPartitionSpecialCharacters() + { + String tableName = "test_sync_partition_special_characters_" + randomNameSuffix(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + String tableLocation = format("s3://%s/%s/%s/", bucketName, HIVE_TEST_SCHEMA, tableName); + + hiveMinioDataLake.getMinioClient().putObject( + bucketName, + "Trino\u0001rocks\u0001hyphens".getBytes(UTF_8), + HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with-hyphen/data.txt"); + hiveMinioDataLake.getMinioClient().putObject( + bucketName, + "Trino\u0001rocks\u0001dots".getBytes(UTF_8), + HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with.dot/data.txt"); + hiveMinioDataLake.getMinioClient().putObject( + bucketName, + "Trino\u0001rocks\u0001colons".getBytes(UTF_8), + HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with%3Acolon/data.txt"); + hiveMinioDataLake.getMinioClient().putObject( + bucketName, + "Trino\u0001rocks\u0001slashes".getBytes(UTF_8), + HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with%2Fslash/data.txt"); + hiveMinioDataLake.getMinioClient().putObject( + bucketName, + "Trino\u0001rocks\u0001backslashes".getBytes(UTF_8), + HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with%5Cbackslash/data.txt"); + hiveMinioDataLake.getMinioClient().putObject( + bucketName, + "Trino\u0001rocks\u0001percents".getBytes(UTF_8), + HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with%25percent/data.txt"); + + assertUpdate("CREATE TABLE " + fullyQualifiedTestTableName + "(" + + " a varchar," + + " b varchar," + + " c varchar," + + " part_key varchar)" + + "WITH (" + + " external_location='" + tableLocation + "'," + + " partitioned_by=ARRAY['part_key']," + + " format='TEXTFILE'" + + ")"); + + getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'ADD')"); + assertQuery( + "SELECT * FROM " + fullyQualifiedTestTableName, + """ + VALUES + ('Trino', 'rocks', 'hyphens', 'with-hyphen'), + ('Trino', 'rocks', 'dots', 'with.dot'), + ('Trino', 'rocks', 'colons', 'with:colon'), + ('Trino', 'rocks', 'slashes', 'with/slash'), + ('Trino', 'rocks', 'backslashes', 'with\\backslash'), + ('Trino', 'rocks', 'percents', 'with%percent') + """); + + assertUpdate("DROP TABLE " + fullyQualifiedTestTableName); + } + + @Test + public void testFlushPartitionCache() + { + String tableName = "nation_" + randomNameSuffix(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + String partitionColumn = "regionkey"; + + testFlushPartitionCache( + tableName, + fullyQualifiedTestTableName, + partitionColumn, + format( + "CALL system.flush_metadata_cache(schema_name => '%s', table_name => '%s', partition_columns => ARRAY['%s'], partition_values => ARRAY['0'])", + HIVE_TEST_SCHEMA, + tableName, + partitionColumn)); + } + + private void testFlushPartitionCache(String tableName, String fullyQualifiedTestTableName, String partitionColumn, String flushCacheProcedureSql) + { + // Create table with partition on regionkey + computeActual(getCreateTableStatement( + fullyQualifiedTestTableName, + format("partitioned_by=ARRAY['%s']", partitionColumn))); + copyTpchNationToTable(fullyQualifiedTestTableName); + + String queryUsingPartitionCacheTemplate = "SELECT name FROM %s WHERE %s=%s"; + String partitionValue1 = "0"; + String queryUsingPartitionCacheForValue1 = format(queryUsingPartitionCacheTemplate, fullyQualifiedTestTableName, partitionColumn, partitionValue1); + String expectedQueryResultForValue1 = "VALUES 'ALGERIA', 'MOROCCO', 'MOZAMBIQUE', 'ETHIOPIA', 'KENYA'"; + String partitionValue2 = "1"; + String queryUsingPartitionCacheForValue2 = format(queryUsingPartitionCacheTemplate, fullyQualifiedTestTableName, partitionColumn, partitionValue2); + String expectedQueryResultForValue2 = "VALUES 'ARGENTINA', 'BRAZIL', 'CANADA', 'PERU', 'UNITED STATES'"; + + // Fill partition cache and check we got expected results + assertQuery(queryUsingPartitionCacheForValue1, expectedQueryResultForValue1); + assertQuery(queryUsingPartitionCacheForValue2, expectedQueryResultForValue2); + + // Copy partition to new location and update metadata outside Trino + renamePartitionResourcesOutsideTrino(tableName, partitionColumn, partitionValue1); + renamePartitionResourcesOutsideTrino(tableName, partitionColumn, partitionValue2); + + // Should return 0 rows as we moved partition and cache is outdated. We use nonexistent partition + assertQueryReturnsEmptyResult(queryUsingPartitionCacheForValue1); + assertQueryReturnsEmptyResult(queryUsingPartitionCacheForValue2); + + // Refresh cache + getQueryRunner().execute(flushCacheProcedureSql); + + // Should return expected rows as we refresh cache + assertQuery(queryUsingPartitionCacheForValue1, expectedQueryResultForValue1); + // Should return 0 rows as we left cache untouched + assertQueryReturnsEmptyResult(queryUsingPartitionCacheForValue2); + + // Refresh cache for schema_name => 'dummy_schema', table_name => 'dummy_table' + getQueryRunner().execute(format( + "CALL system.flush_metadata_cache(schema_name => '%s', table_name => '%s')", + HIVE_TEST_SCHEMA, + tableName)); + + // Should return expected rows for all partitions + assertQuery(queryUsingPartitionCacheForValue1, expectedQueryResultForValue1); + assertQuery(queryUsingPartitionCacheForValue2, expectedQueryResultForValue2); + + computeActual(format("DROP TABLE %s", fullyQualifiedTestTableName)); + } + + @Test + public void testWriteDifferentSizes() + { + String testTable = getFullyQualifiedTestTableName(); + computeActual(format( + "CREATE TABLE %s (" + + " col1 varchar, " + + " col2 varchar, " + + " regionkey bigint) " + + " WITH (partitioned_by=ARRAY['regionkey'])", + testTable)); + + long partSizeInBytes = HIVE_S3_STREAMING_PART_SIZE.toBytes(); + + // Exercise different code paths of Hive S3 streaming upload, with upload part size 5MB: + // 1. fileSize <= 5MB (direct upload) + testWriteWithFileSize(testTable, 50, 0, partSizeInBytes); + + // 2. 5MB < fileSize <= 10MB (upload in two parts) + testWriteWithFileSize(testTable, 100, partSizeInBytes + 1, partSizeInBytes * 2); + + // 3. fileSize > 10MB (upload in three or more parts) + testWriteWithFileSize(testTable, 150, partSizeInBytes * 2 + 1, partSizeInBytes * 3); + + computeActual(format("DROP TABLE %s", testTable)); + } + + @Test + public void testEnumPartitionProjectionOnVarcharColumnWithWhitespace() + { + String tableName = "nation_" + randomNameSuffix(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " (" + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " \"short name\" varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short name'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short name\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short name\\.values[ |]+PL1,CZ1[ |]+"); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL2'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ2'")))); + + assertQuery( + format("SELECT * FROM %s", getFullyQualifiedTestTableName("\"" + tableName + "$partitions\"")), + "VALUES 'PL1', 'CZ1'"); + + assertQuery( + format("SELECT name FROM %s WHERE \"short name\"='PL1'", fullyQualifiedTestTableName), + "VALUES 'POLAND_1'"); + + // No results should be returned as Partition Projection will not project partitions for this value + assertQueryReturnsEmptyResult( + format("SELECT name FROM %s WHERE \"short name\"='PL2'", fullyQualifiedTestTableName)); + + assertQuery( + format("SELECT name FROM %s WHERE \"short name\"='PL1' OR \"short name\"='CZ1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('CZECH_1')"); + + // Only POLAND_1 row will be returned as other value is outside of projection + assertQuery( + format("SELECT name FROM %s WHERE \"short name\"='PL1' OR \"short name\"='CZ2'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1')"); + + // All values within projection range will be returned + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('CZECH_1')"); + } + + @Test + public void testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplateCreatedOnTrino() + { + // It's important to mix case here to detect if we properly handle rewriting + // properties between Trino and Hive (e.g for Partition Projection) + String schemaName = "Hive_Datalake_MixedCase"; + String tableName = getRandomTestTableName(); + + // We create new schema to include mixed case location path and create such keys in Object Store + computeActual("CREATE SCHEMA hive.%1$s WITH (location='s3a://%2$s/%1$s')".formatted(schemaName, bucketName)); + + String storageFormat = format( + "s3a://%s/%s/%s/short_name1=${short_name1}/short_name2=${short_name2}/", + this.bucketName, + schemaName, + tableName); + computeActual( + "CREATE TABLE " + getFullyQualifiedTestTableName(schemaName, tableName) + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL2', 'CZ2'] " + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true, " + + " partition_projection_location_template='" + storageFormat + "' " + + ")"); + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(schemaName, tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+storage\\.location\\.template[ |]+" + quote(storageFormat) + "[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.values[ |]+PL2,CZ2[ |]+"); + testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplate(schemaName, tableName); + } + + @Test + public void testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplateCreatedOnHive() + { + String tableName = getRandomTestTableName(); + String storageFormat = format( + "'s3a://%s/%s/%s/short_name1=${short_name1}/short_name2=${short_name2}/'", + this.bucketName, + HIVE_TEST_SCHEMA, + tableName); + hiveMinioDataLake.runOnHive( + "CREATE TABLE " + getHiveTestTableName(tableName) + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint " + + ") PARTITIONED BY (" + + " short_name1 varchar(152), " + + " short_name2 varchar(152)" + + ") " + + "TBLPROPERTIES ( " + + " 'projection.enabled'='true', " + + " 'storage.location.template'=" + storageFormat + ", " + + " 'projection.short_name1.type'='enum', " + + " 'projection.short_name1.values'='PL1,CZ1', " + + " 'projection.short_name2.type'='enum', " + + " 'projection.short_name2.values'='PL2,CZ2' " + + ")"); + testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplate(HIVE_TEST_SCHEMA, tableName); + } + + private void testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplate(String schemaName, String tableName) + { + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(schemaName, tableName); + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'PL2'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'CZ2'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'PL2'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'CZ2'")))); + + assertQuery( + format("SELECT * FROM %s", getFullyQualifiedTestTableName(schemaName, "\"" + tableName + "$partitions\"")), + "VALUES ('PL1','PL2'), ('PL1','CZ2'), ('CZ1','PL2'), ('CZ1','CZ2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='CZ2'", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testEnumPartitionProjectionOnVarcharColumn() + { + String tableName = getRandomTestTableName(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL2', 'CZ2']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.values[ |]+PL2,CZ2[ |]+"); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'PL2'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'CZ2'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'PL2'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'CZ2'")))); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='CZ2'", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='CZ2' OR short_name2='PL2' )", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlignCreatedOnTrino() + { + String tableName = getRandomTestTableName(); + computeActual( + "CREATE TABLE " + getFullyQualifiedTestTableName(tableName) + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 varchar(152) WITH (" + + " partition_projection_type='integer', " + + " partition_projection_range=ARRAY['1', '4'], " + + " partition_projection_digits=3" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+integer[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+1,4[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.digits[ |]+3[ |]+"); + testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlign(tableName); + } + + @Test + public void testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlignCreatedOnHive() + { + String tableName = "nation_" + randomNameSuffix(); + hiveMinioDataLake.runOnHive( + "CREATE TABLE " + getHiveTestTableName(tableName) + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint " + + ") " + + "PARTITIONED BY ( " + + " short_name1 varchar(152), " + + " short_name2 varchar(152)" + + ") " + + "TBLPROPERTIES " + + "( " + + " 'projection.enabled'='true', " + + " 'projection.short_name1.type'='enum', " + + " 'projection.short_name1.values'='PL1,CZ1', " + + " 'projection.short_name2.type'='integer', " + + " 'projection.short_name2.range'='1,4', " + + " 'projection.short_name2.digits'='3'" + + ")"); + testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlign(tableName); + } + + private void testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlign(String tableName) + { + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'001'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'002'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'003'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'004'")))); + + assertQuery( + format("SELECT * FROM %s", getFullyQualifiedTestTableName("\"" + tableName + "$partitions\"")), + "VALUES ('PL1','001'), ('PL1','002'), ('PL1','003'), ('PL1','004')," + + "('CZ1','001'), ('CZ1','002'), ('CZ1','003'), ('CZ1','004')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='002'", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='002' OR short_name2='001' )", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testIntegerPartitionProjectionOnIntegerColumnWithInterval() + { + String tableName = getRandomTestTableName(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 integer WITH (" + + " partition_projection_type='integer', " + + " partition_projection_range=ARRAY['0', '10'], " + + " partition_projection_interval=3" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+integer[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+0,10[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+3[ |]+"); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "0"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "3"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "6"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "9")))); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2=3", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2=3 OR short_name2=0 )", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testIntegerPartitionProjectionOnIntegerColumnWithDefaults() + { + String tableName = getRandomTestTableName(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 integer WITH (" + + " partition_projection_type='integer', " + + " partition_projection_range=ARRAY['1', '4']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+integer[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+1,4[ |]+"); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "1"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "2"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "3"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "4")))); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2=2", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2=2 OR short_name2=1 )", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testDatePartitionProjectionOnDateColumnWithDefaults() + { + String tableName = "nation_" + randomNameSuffix(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 date WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd', " + + " partition_projection_range=ARRAY['2001-1-22', '2001-1-25']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+yyyy-MM-dd[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+2001-1-22,2001-1-25[ |]+"); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "DATE '2001-1-22'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "DATE '2001-1-23'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "DATE '2001-1-24'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "DATE '2001-1-25'"), + ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "DATE '2001-1-26'")))); + + assertQuery( + format("SELECT * FROM %s", getFullyQualifiedTestTableName("\"" + tableName + "$partitions\"")), + "VALUES ('PL1','2001-1-22'), ('PL1','2001-1-23'), ('PL1','2001-1-24'), ('PL1','2001-1-25')," + + "('CZ1','2001-1-22'), ('CZ1','2001-1-23'), ('CZ1','2001-1-24'), ('CZ1','2001-1-25')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2=(DATE '2001-1-23')", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2=(DATE '2001-1-23') OR short_name2=(DATE '2001-1-22') )", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 > DATE '2001-1-23'", fullyQualifiedTestTableName), + "VALUES ('CZECH_1'), ('CZECH_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 >= DATE '2001-1-23' AND short_name2 <= DATE '2001-1-25'", fullyQualifiedTestTableName), + "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testDatePartitionProjectionOnTimestampColumnWithInterval() + { + String tableName = getRandomTestTableName(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 timestamp WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd HH:mm:ss', " + + " partition_projection_range=ARRAY['2001-1-22 00:00:00', '2001-1-22 00:00:06'], " + + " partition_projection_interval=2, " + + " partition_projection_interval_unit='SECONDS'" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+yyyy-MM-dd HH:mm:ss[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+2001-1-22 00:00:00,2001-1-22 00:00:06[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+2[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.interval\\.unit[ |]+seconds[ |]+"); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "TIMESTAMP '2001-1-22 00:00:00'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "TIMESTAMP '2001-1-22 00:00:02'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "TIMESTAMP '2001-1-22 00:00:04'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "TIMESTAMP '2001-1-22 00:00:06'"), + ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "TIMESTAMP '2001-1-22 00:00:08'")))); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2=(TIMESTAMP '2001-1-22 00:00:02')", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2=(TIMESTAMP '2001-1-22 00:00:00') OR short_name2=(TIMESTAMP '2001-1-22 00:00:02') )", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 > TIMESTAMP '2001-1-22 00:00:02'", fullyQualifiedTestTableName), + "VALUES ('CZECH_1'), ('CZECH_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 >= TIMESTAMP '2001-1-22 00:00:02' AND short_name2 <= TIMESTAMP '2001-1-22 00:00:06'", fullyQualifiedTestTableName), + "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testDatePartitionProjectionOnTimestampColumnWithIntervalExpressionCreatedOnTrino() + { + String tableName = getRandomTestTableName(); + String dateProjectionFormat = "yyyy-MM-dd HH:mm:ss"; + computeActual( + "CREATE TABLE " + getFullyQualifiedTestTableName(tableName) + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 timestamp WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='" + dateProjectionFormat + "', " + + // We set range to -5 minutes to NOW in order to be sure it will grab all test dates + // which range is -4 minutes till now. Also, we have to consider max no. of partitions 1k + " partition_projection_range=ARRAY['NOW-5MINUTES', 'NOW'], " + + " partition_projection_interval=1, " + + " partition_projection_interval_unit='SECONDS'" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+" + quote(dateProjectionFormat) + "[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+NOW-5MINUTES,NOW[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.interval\\.unit[ |]+seconds[ |]+"); + testDatePartitionProjectionOnTimestampColumnWithIntervalExpression(tableName, dateProjectionFormat); + } + + @Test + public void testDatePartitionProjectionOnTimestampColumnWithIntervalExpressionCreatedOnHive() + { + String tableName = getRandomTestTableName(); + String dateProjectionFormat = "yyyy-MM-dd HH:mm:ss"; + hiveMinioDataLake.runOnHive( + "CREATE TABLE " + getHiveTestTableName(tableName) + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint " + + ") " + + "PARTITIONED BY (" + + " short_name1 varchar(152), " + + " short_name2 timestamp " + + ") " + + "TBLPROPERTIES ( " + + " 'projection.enabled'='true', " + + " 'projection.short_name1.type'='enum', " + + " 'projection.short_name1.values'='PL1,CZ1', " + + " 'projection.short_name2.type'='date', " + + " 'projection.short_name2.format'='" + dateProjectionFormat + "', " + + // We set range to -5 minutes to NOW in order to be sure it will grab all test dates + // which range is -4 minutes till now. Also, we have to consider max no. of partitions 1k + " 'projection.short_name2.range'='NOW-5MINUTES,NOW', " + + " 'projection.short_name2.interval'='1', " + + " 'projection.short_name2.interval.unit'='SECONDS'" + + ")"); + testDatePartitionProjectionOnTimestampColumnWithIntervalExpression(tableName, dateProjectionFormat); + } + + private void testDatePartitionProjectionOnTimestampColumnWithIntervalExpression(String tableName, String dateProjectionFormat) + { + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + Instant dayToday = Instant.now(); + DateFormat dateFormat = new SimpleDateFormat(dateProjectionFormat); + dateFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of("UTC"))); + String minutesNowFormatted = moveDate(dateFormat, dayToday, MINUTES, 0); + String minutes1AgoFormatter = moveDate(dateFormat, dayToday, MINUTES, -1); + String minutes2AgoFormatted = moveDate(dateFormat, dayToday, MINUTES, -2); + String minutes3AgoFormatted = moveDate(dateFormat, dayToday, MINUTES, -3); + String minutes4AgoFormatted = moveDate(dateFormat, dayToday, MINUTES, -4); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "TIMESTAMP '" + minutesNowFormatted + "'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "TIMESTAMP '" + minutes1AgoFormatter + "'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "TIMESTAMP '" + minutes2AgoFormatted + "'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "TIMESTAMP '" + minutes3AgoFormatted + "'"), + ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "TIMESTAMP '" + minutes4AgoFormatted + "'")))); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 > ( TIMESTAMP '%s' ) AND short_name2 <= ( TIMESTAMP '%s' )", fullyQualifiedTestTableName, minutes4AgoFormatted, minutes1AgoFormatter), + "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testDatePartitionProjectionOnVarcharColumnWithHoursInterval() + { + String tableName = getRandomTestTableName(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd HH', " + + " partition_projection_range=ARRAY['2001-01-22 00', '2001-01-22 06'], " + + " partition_projection_interval=2, " + + " partition_projection_interval_unit='HOURS'" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+yyyy-MM-dd HH[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+2001-01-22 00,2001-01-22 06[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+2[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.interval\\.unit[ |]+hours[ |]+"); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'2001-01-22 00'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'2001-01-22 02'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'2001-01-22 04'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'2001-01-22 06'"), + ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "'2001-01-22 08'")))); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='2001-01-22 02'", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='2001-01-22 00' OR short_name2='2001-01-22 02' )", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 > '2001-01-22 02'", fullyQualifiedTestTableName), + "VALUES ('CZECH_1'), ('CZECH_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 >= '2001-01-22 02' AND short_name2 <= '2001-01-22 06'", fullyQualifiedTestTableName), + "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testDatePartitionProjectionOnVarcharColumnWithDaysInterval() + { + String tableName = getRandomTestTableName(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd', " + + " partition_projection_range=ARRAY['2001-01-01', '2001-01-07'], " + + " partition_projection_interval=2, " + + " partition_projection_interval_unit='DAYS'" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+yyyy-MM-dd[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+2001-01-01,2001-01-07[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+2[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.interval\\.unit[ |]+days[ |]+"); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'2001-01-01'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'2001-01-03'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'2001-01-05'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'2001-01-07'"), + ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "'2001-01-09'")))); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='2001-01-03'", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='2001-01-01' OR short_name2='2001-01-03' )", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 > '2001-01-03'", fullyQualifiedTestTableName), + "VALUES ('CZECH_1'), ('CZECH_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 >= '2001-01-03' AND short_name2 <= '2001-01-07'", fullyQualifiedTestTableName), + "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + @Test + public void testDatePartitionProjectionOnVarcharColumnWithIntervalExpression() + { + String tableName = getRandomTestTableName(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + String dateProjectionFormat = "yyyy-MM-dd"; + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='" + dateProjectionFormat + "', " + + " partition_projection_range=ARRAY['NOW-3DAYS', 'NOW']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+" + quote(dateProjectionFormat) + "[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+NOW-3DAYS,NOW[ |]+"); + + Instant dayToday = Instant.now(); + DateFormat dateFormat = new SimpleDateFormat(dateProjectionFormat); + dateFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of("UTC"))); + String dayTodayFormatted = moveDate(dateFormat, dayToday, DAYS, 0); + String day1AgoFormatter = moveDate(dateFormat, dayToday, DAYS, -1); + String day2AgoFormatted = moveDate(dateFormat, dayToday, DAYS, -2); + String day3AgoFormatted = moveDate(dateFormat, dayToday, DAYS, -3); + String day4AgoFormatted = moveDate(dateFormat, dayToday, DAYS, -4); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'" + dayTodayFormatted + "'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'" + day1AgoFormatter + "'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'" + day2AgoFormatted + "'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'" + day3AgoFormatted + "'"), + ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "'" + day4AgoFormatted + "'")))); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='%s'", fullyQualifiedTestTableName, day1AgoFormatter), + "VALUES 'POLAND_2'"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='%s' OR short_name2='%s' )", fullyQualifiedTestTableName, dayTodayFormatted, day1AgoFormatter), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 > '%s'", fullyQualifiedTestTableName, day2AgoFormatted), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name2 >= '%s' AND short_name2 <= '%s'", fullyQualifiedTestTableName, day4AgoFormatted, day1AgoFormatter), + "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2')"); + + assertQuery( + format("SELECT name FROM %s", fullyQualifiedTestTableName), + "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); + } + + private String moveDate(DateFormat format, Instant today, TemporalUnit unit, int move) + { + return format.format(new Date(today.plus(move, unit).toEpochMilli())); + } + + @Test + public void testDatePartitionProjectionFormatTextWillNotCauseIntervalRequirement() + { + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='''start''yyyy-MM-dd''end''''s''', " + + " partition_projection_range=ARRAY['start2001-01-01end''s', 'start2001-01-07end''s'] " + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'], " + + " partition_projection_enabled=true " + + ")"); + } + + @Test + public void testInjectedPartitionProjectionOnVarcharColumn() + { + String tableName = getRandomTestTableName(); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + computeActual( + "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint, " + + " short_name1 varchar(152) WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 varchar(152) WITH (" + + " partition_projection_type='injected'" + + " ) " + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")"); + + assertThat( + hiveMinioDataLake + .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) + .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") + .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") + .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+injected[ |]+"); + + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'001'"), + ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'002'"), + ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'003'"), + ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'004'")))); + + assertQuery( + format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='002'", fullyQualifiedTestTableName), + "VALUES 'POLAND_2'"); + + assertThatThrownBy( + () -> getQueryRunner().execute( + format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='002' OR short_name2='001' )", fullyQualifiedTestTableName))) + .hasMessage("Column projection for column 'short_name2' failed. Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); + + assertThatThrownBy( + () -> getQueryRunner().execute( + format("SELECT name FROM %s", fullyQualifiedTestTableName))) + .hasMessage("Column projection for column 'short_name2' failed. Injected projection requires single predicate for it's column in where clause"); + + assertThatThrownBy( + () -> getQueryRunner().execute( + format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName))) + .hasMessage("Column projection for column 'short_name2' failed. Injected projection requires single predicate for it's column in where clause"); + } + + @Test + public void testPartitionProjectionInvalidTableProperties() + { + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar " + + ") WITH ( " + + " partition_projection_enabled=true " + + ")")) + .hasMessage("Partition projection cannot be enabled on a table that is not partitioned"); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar WITH ( " + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1']" + + " ), " + + " short_name1 varchar " + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'], " + + " partition_projection_enabled=true " + + ")")) + .hasMessage("Partition projection cannot be defined for non-partition column: 'name'"); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH ( " + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1']" + + " ), " + + " short_name2 varchar " + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")")) + .hasMessage("Column projection for column 'short_name2' failed. Projection type property missing"); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " ), " + + " short_name2 varchar WITH (" + + " partition_projection_type='injected' " + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true, " + + " partition_projection_location_template='s3a://dummy/short_name1=${short_name1}/'" + + ")")) + .hasMessage("Partition projection location template: s3a://dummy/short_name1=${short_name1}/ " + + "is missing partition column: 'short_name2' placeholder"); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='integer', " + + " partition_projection_range=ARRAY['1', '2', '3']" + + " ), " + + " short_name2 varchar WITH (" + + " partition_projection_type='enum', " + + " partition_projection_values=ARRAY['PL1', 'CZ1'] " + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1', 'short_name2'], " + + " partition_projection_enabled=true " + + ")")) + .hasMessage("Column projection for column 'short_name1' failed. Property: 'partition_projection_range' needs to be list of 2 integers"); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_values=ARRAY['2001-01-01', '2001-01-02']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'], " + + " partition_projection_enabled=true " + + ")")) + .hasMessage("Column projection for column 'short_name1' failed. Missing required property: 'partition_projection_format'"); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd HH', " + + " partition_projection_range=ARRAY['2001-01-01', '2001-01-02']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'], " + + " 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\""); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd', " + + " partition_projection_range=ARRAY['NOW*3DAYS', '2001-01-02']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'], " + + " 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\""); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd', " + + " partition_projection_range=ARRAY['2001-01-02', '2001-01-01']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'], " + + " 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"); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd', " + + " partition_projection_range=ARRAY['2001-01-01', '2001-01-02'], " + + " partition_projection_interval_unit='Decades'" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'], " + + " partition_projection_enabled=true " + + ")")) + .hasMessage("Column projection for column 'short_name1' failed. Property: 'partition_projection_interval_unit' value 'Decades' is invalid. " + + "Available options: [Days, Hours, Minutes, Seconds]"); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd HH', " + + " partition_projection_range=ARRAY['2001-01-01 10', '2001-01-02 10']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'], " + + " partition_projection_enabled=true " + + ")")) + .hasMessage("Column projection for column 'short_name1' failed. Property: 'partition_projection_interval_unit' " + + "needs to be set when provided 'partition_projection_format' is less that single-day precision. " + + "Interval defaults to 1 day or 1 month, respectively. Otherwise, interval is required"); + + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd', " + + " partition_projection_range=ARRAY['2001-01-01 10', '2001-01-02 10']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'] " + + ")")) + .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. + assertThatThrownBy(() -> getQueryRunner().execute( + "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + + " name varchar, " + + " short_name1 varchar WITH (" + + " partition_projection_type='date', " + + " partition_projection_format='yyyy-MM-dd HH', " + + " partition_projection_range=ARRAY['2001-01-01 10', '2001-01-02 10']" + + " )" + + ") WITH ( " + + " partitioned_by=ARRAY['short_name1'], " + + " 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' " + + "needs to be set when provided 'partition_projection_format' is less that single-day precision. " + + "Interval defaults to 1 day or 1 month, respectively. Otherwise, interval is required"); + } + + @Test + public void testPartitionProjectionIgnore() + { + String tableName = "nation_" + randomNameSuffix(); + String hiveTestTableName = getHiveTestTableName(tableName); + String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); + + // Create corrupted configuration + hiveMinioDataLake.runOnHive( + "CREATE TABLE " + hiveTestTableName + " ( " + + " name varchar(25) " + + ") PARTITIONED BY (" + + " date_time varchar(152) " + + ") " + + "TBLPROPERTIES ( " + + " 'projection.enabled'='true', " + + " 'projection.date_time.type'='date', " + + " 'projection.date_time.format'='yyyy-MM-dd HH', " + + " 'projection.date_time.range'='2001-01-01,2001-01-02' " + + ")"); + + // 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\""); + + // Append kill switch table property to ignore Partition Projection properties + hiveMinioDataLake.runOnHive( + "ALTER TABLE " + hiveTestTableName + " SET TBLPROPERTIES ( 'trino.partition_projection.ignore'='TRUE' )"); + // Flush cache to get new definition + computeActual("CALL system.flush_metadata_cache(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "')"); + + // Verify query execution works + computeActual(createInsertStatement( + fullyQualifiedTestTableName, + ImmutableList.of( + ImmutableList.of("'POLAND_1'", "'2022-02-01 12'"), + ImmutableList.of("'POLAND_2'", "'2022-02-01 12'"), + ImmutableList.of("'CZECH_1'", "'2022-02-01 13'"), + ImmutableList.of("'CZECH_2'", "'2022-02-01 13'")))); + + assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, + "VALUES ('POLAND_1', '2022-02-01 12'), " + + "('POLAND_2', '2022-02-01 12'), " + + "('CZECH_1', '2022-02-01 13'), " + + "('CZECH_2', '2022-02-01 13')"); + assertQuery("SELECT * FROM " + fullyQualifiedTestTableName + " WHERE date_time = '2022-02-01 12'", + "VALUES ('POLAND_1', '2022-02-01 12'), ('POLAND_2', '2022-02-01 12')"); + } + + @Test + public void testAnalyzePartitionedTableWithCanonicalization() + { + String tableName = "test_analyze_table_canonicalization_" + randomNameSuffix(); + assertUpdate("CREATE TABLE %s (a_varchar varchar, month varchar) WITH (partitioned_by = ARRAY['month'])".formatted(getFullyQualifiedTestTableName(tableName))); + + assertUpdate("INSERT INTO " + getFullyQualifiedTestTableName(tableName) + " VALUES ('A', '01'), ('B', '01'), ('C', '02'), ('D', '03')", 4); + + String tableLocation = (String) computeActual("SELECT DISTINCT regexp_replace(\"$path\", '/[^/]*/[^/]*$', '') FROM " + getFullyQualifiedTestTableName(tableName)).getOnlyValue(); + + String externalTableName = "external_" + tableName; + List partitionColumnNames = List.of("month"); + assertUpdate( + """ + CREATE TABLE %s( + a_varchar varchar, + month integer) + WITH ( + partitioned_by = ARRAY['month'], + external_location='%s') + """.formatted(getFullyQualifiedTestTableName(externalTableName), tableLocation)); + + addPartitions(tableName, externalTableName, partitionColumnNames, TupleDomain.all()); + assertQuery("SELECT * FROM " + HIVE_TEST_SCHEMA + ".\"" + externalTableName + "$partitions\"", "VALUES 1, 2, 3"); + assertUpdate("ANALYZE " + getFullyQualifiedTestTableName(externalTableName), 4); + assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(externalTableName), + """ + VALUES + ('a_varchar', 4.0, 2.0, 0.0, null, null, null), + ('month', null, 3.0, 0.0, null, 1, 3), + (null, null, null, null, 4.0, null, null) + """); + + assertUpdate("INSERT INTO " + getFullyQualifiedTestTableName(tableName) + " VALUES ('E', '04')", 1); + addPartitions( + tableName, + externalTableName, + partitionColumnNames, + TupleDomain.fromFixedValues(Map.of("month", new NullableValue(VARCHAR, utf8Slice("04"))))); + assertUpdate("CALL system.flush_metadata_cache(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + externalTableName + "')"); + assertQuery("SELECT * FROM " + HIVE_TEST_SCHEMA + ".\"" + externalTableName + "$partitions\"", "VALUES 1, 2, 3, 4"); + assertUpdate("ANALYZE " + getFullyQualifiedTestTableName(externalTableName) + " WITH (partitions = ARRAY[ARRAY['04']])", 1); + assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(externalTableName), + """ + VALUES + ('a_varchar', 5.0, 2.0, 0.0, null, null, null), + ('month', null, 4.0, 0.0, null, 1, 4), + (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.*not found.*"); + + assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(externalTableName)); + assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(tableName)); + } + + @Test + public void testExternalLocationWithTrailingSpace() + { + String tableName = "test_external_location_with_trailing_space_" + randomNameSuffix(); + String tableLocationDirWithTrailingSpace = tableName + " "; + String tableLocation = format("s3a://%s/%s/%s", bucketName, HIVE_TEST_SCHEMA, tableLocationDirWithTrailingSpace); + + byte[] contents = "hello\u0001world\nbye\u0001world".getBytes(UTF_8); + String targetPath = format("%s/%s/test.txt", HIVE_TEST_SCHEMA, tableLocationDirWithTrailingSpace); + hiveMinioDataLake.getMinioClient().putObject(bucketName, contents, targetPath); + + assertUpdate(format( + "CREATE TABLE %s (" + + " a varchar, " + + " b varchar) " + + "WITH (format='TEXTFILE', external_location='%s')", + tableName, + tableLocation)); + + assertQuery("SELECT a, b FROM " + tableName, "VALUES ('hello', 'world'), ('bye', 'world')"); + + String actualTableLocation = getTableLocation(tableName); + assertThat(actualTableLocation).isEqualTo(tableLocation); + + assertUpdate("DROP TABLE " + tableName); + } + + @Test + public void testCreateSchemaInvalidName() + { + assertThatThrownBy(() -> assertUpdate("CREATE SCHEMA \".\"")) + .hasMessage("Invalid object name: '.'"); + + assertThatThrownBy(() -> assertUpdate("CREATE SCHEMA \"..\"")) + .hasMessage("Invalid object name: '..'"); + + assertThatThrownBy(() -> assertUpdate("CREATE SCHEMA \"foo/bar\"")) + .hasMessage("Invalid object name: 'foo/bar'"); + } + + @Test + public void testCreateTableInvalidName() + { + assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\".\" (col integer)")) + .hasMessageContaining("Invalid table name"); + assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\"..\" (col integer)")) + .hasMessageContaining("Invalid table name"); + assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\"...\" (col integer)")) + .hasMessage("Invalid table name"); + + for (String tableName : Arrays.asList("foo/bar", "foo/./bar", "foo/../bar")) { + assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\"" + tableName + "\" (col integer)")) + .hasMessage(format("Invalid object name: '%s'", tableName)); + assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\"" + tableName + "\" (col) AS VALUES 1")) + .hasMessage(format("Invalid object name: '%s'", tableName)); + } + } + + @Test + public void testRenameSchemaToInvalidObjectName() + { + String schemaName = "test_rename_schema_invalid_name_" + randomNameSuffix(); + 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 + "\"")) + .hasMessage(format("Invalid object name: '%s'", invalidSchemaName)); + } + + assertUpdate("DROP SCHEMA " + schemaName); + } + + @Test + public void testRenameTableToInvalidObjectName() + { + String tableName = "test_rename_table_invalid_name_" + randomNameSuffix(); + assertUpdate("CREATE TABLE %s (a_varchar varchar)".formatted(getFullyQualifiedTestTableName(tableName))); + + for (String invalidTableName : Arrays.asList(".", "..", "foo/bar")) { + assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + getFullyQualifiedTestTableName(tableName) + " RENAME TO \"" + invalidTableName + "\"")) + .hasMessage(format("Invalid object name: '%s'", invalidTableName)); + } + + for (String invalidSchemaName : Arrays.asList(".", "..", "foo/bar")) { + assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + getFullyQualifiedTestTableName(tableName) + " RENAME TO \"" + invalidSchemaName + "\".validTableName")) + .hasMessage(format("Invalid object name: '%s'", invalidSchemaName)); + } + + assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(tableName)); + } + + @Test + public void testUnpartitionedTableExternalLocationWithTrainingSlash() + { + String tableName = "test_external_location_trailing_slash_" + randomNameSuffix(); + String tableLocationWithTrailingSlash = format("s3://%s/%s/%s/", bucketName, HIVE_TEST_SCHEMA, tableName); + byte[] contents = "Trino\nSQL\non\neverything".getBytes(UTF_8); + String dataFilePath = format("%s/%s/data.txt", HIVE_TEST_SCHEMA, tableName); + hiveMinioDataLake.getMinioClient().putObject(bucketName, contents, dataFilePath); + + assertUpdate(format( + "CREATE TABLE %s (" + + " a_varchar varchar) " + + "WITH (" + + " external_location='%s'," + + " format='TEXTFILE')", + tableName, + tableLocationWithTrailingSlash)); + assertQuery("SELECT * FROM " + tableName, "VALUES 'Trino', 'SQL', 'on', 'everything'"); + + assertUpdate("DROP TABLE " + tableName); + } + + @Test + public void testUnpartitionedTableExternalLocationOnTopOfTheBucket() + { + String topBucketName = "test-hive-unpartitioned-top-of-the-bucket-" + randomNameSuffix(); + hiveMinioDataLake.getMinio().createBucket(topBucketName); + String tableName = "test_external_location_top_of_the_bucket_" + randomNameSuffix(); + + byte[] contents = "Trino\nSQL\non\neverything".getBytes(UTF_8); + hiveMinioDataLake.getMinioClient().putObject(topBucketName, contents, "data.txt"); + + assertUpdate(format( + "CREATE TABLE %s (" + + " a_varchar varchar) " + + "WITH (" + + " external_location='%s'," + + " format='TEXTFILE')", + tableName, + format("s3://%s/", topBucketName))); + assertQuery("SELECT * FROM " + tableName, "VALUES 'Trino', 'SQL', 'on', 'everything'"); + + assertUpdate("DROP TABLE " + tableName); + } + + @Test + public void testPartitionedTableExternalLocationOnTopOfTheBucket() + { + String topBucketName = "test-hive-partitioned-top-of-the-bucket-" + randomNameSuffix(); + hiveMinioDataLake.getMinio().createBucket(topBucketName); + String tableName = "test_external_location_top_of_the_bucket_" + randomNameSuffix(); + + assertUpdate(format( + "CREATE TABLE %s (" + + " a_varchar varchar, " + + " pkey integer) " + + "WITH (" + + " external_location='%s'," + + " partitioned_by=ARRAY['pkey'])", + tableName, + format("s3://%s/", topBucketName))); + assertUpdate("INSERT INTO " + tableName + " VALUES ('a', 1) , ('b', 1), ('c', 2), ('d', 2)", 4); + assertQuery("SELECT * FROM " + tableName, "VALUES ('a', 1), ('b',1), ('c', 2), ('d', 2)"); + assertUpdate("DELETE FROM " + tableName + " where pkey = 2"); + assertQuery("SELECT * FROM " + tableName, "VALUES ('a', 1), ('b',1)"); + + assertUpdate("DROP TABLE " + tableName); + } + + @Test + public void testDropStatsPartitionedTable() + { + String tableName = "test_hive_drop_stats_partitioned_table_" + randomNameSuffix(); + assertUpdate(("CREATE TABLE %s (" + + " data integer," + + " p_varchar varchar," + + " p_integer integer" + + ") " + + "WITH (" + + " partitioned_by=ARRAY['p_varchar', 'p_integer']" + + ")").formatted(getFullyQualifiedTestTableName(tableName))); + + // Drop stats for partition which does not exist + assertThat(query(format("CALL system.drop_stats('%s', '%s', ARRAY[ARRAY['partnotfound', '999']])", HIVE_TEST_SCHEMA, tableName))) + .failure().hasMessage("No partition found for name: p_varchar=partnotfound/p_integer=999"); + + assertUpdate("INSERT INTO " + getFullyQualifiedTestTableName(tableName) + " VALUES (1, 'part1', 10) , (2, 'part2', 10), (12, 'part2', 20)", 3); + + // Run analyze on the entire table + assertUpdate("ANALYZE " + getFullyQualifiedTestTableName(tableName), 3); + + assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(tableName), + """ + VALUES + ('data', null, 1.0, 0.0, null, 1, 12), + ('p_varchar', 15.0, 2.0, 0.0, null, null, null), + ('p_integer', null, 2.0, 0.0, null, 10, 20), + (null, null, null, null, 3.0, null, null) + """); + + assertUpdate(format("CALL system.drop_stats('%s', '%s', ARRAY[ARRAY['part1', '10']])", HIVE_TEST_SCHEMA, tableName)); + + assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(tableName), + """ + VALUES + ('data', null, 1.0, 0.0, null, 2, 12), + ('p_varchar', 15.0, 2.0, 0.0, null, null, null), + ('p_integer', null, 2.0, 0.0, null, 10, 20), + (null, null, null, null, 3.0, null, null) + """); + + assertUpdate("DELETE FROM " + getFullyQualifiedTestTableName(tableName) + " WHERE p_varchar ='part1' and p_integer = 10"); + + // Drop stats for partition which does not exist + assertThat(query(format("CALL system.drop_stats('%s', '%s', ARRAY[ARRAY['part1', '10']])", HIVE_TEST_SCHEMA, tableName))) + .failure().hasMessage("No partition found for name: p_varchar=part1/p_integer=10"); + + assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(tableName), + """ + VALUES + ('data', null, 1.0, 0.0, null, 2, 12), + ('p_varchar', 10.0, 1.0, 0.0, null, null, null), + ('p_integer', null, 2.0, 0.0, null, 10, 20), + (null, null, null, null, 2.0, null, null) + """); + assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(tableName)); + } + + @Test + public void testUnsupportedDropSchemaCascadeWithNonHiveTable() + { + String schemaName = "test_unsupported_drop_schema_cascade_" + randomNameSuffix(); + String icebergTableName = "test_dummy_iceberg_table" + randomNameSuffix(); + + hiveMinioDataLake.runOnHive("CREATE DATABASE %2$s LOCATION 's3a://%1$s/%2$s'".formatted(bucketName, schemaName)); + try { + hiveMinioDataLake.runOnHive("CREATE TABLE " + schemaName + "." + icebergTableName + " TBLPROPERTIES ('table_type'='iceberg') AS SELECT 1 a"); + + assertQueryFails("DROP SCHEMA " + schemaName + " CASCADE", "\\QCannot query Iceberg table '%s.%s'".formatted(schemaName, icebergTableName)); + + assertThat(computeActual("SHOW SCHEMAS").getOnlyColumnAsSet()).contains(schemaName); + assertThat(computeActual("SHOW TABLES FROM " + schemaName).getOnlyColumnAsSet()).contains(icebergTableName); + assertThat(hiveMinioDataLake.getMinioClient().listObjects(bucketName, schemaName).stream()).isNotEmpty(); + } + finally { + hiveMinioDataLake.runOnHive("DROP DATABASE IF EXISTS " + schemaName + " CASCADE"); + } + } + + @Test + public void testUnsupportedCommentOnHiveView() + { + String viewName = HIVE_TEST_SCHEMA + ".test_unsupported_comment_on_hive_view_" + randomNameSuffix(); + + hiveMinioDataLake.runOnHive("CREATE VIEW " + viewName + " AS SELECT 1 x"); + try { + assertQueryFails("COMMENT ON COLUMN " + viewName + ".x IS NULL", "Hive views are not supported.*"); + } + finally { + hiveMinioDataLake.runOnHive("DROP VIEW " + viewName); + } + } + + @Test + public void testCreateFunction() + { + String name = "test_" + randomNameSuffix(); + String name2 = "test_" + randomNameSuffix(); + + assertUpdate("CREATE FUNCTION " + name + "(x integer) RETURNS bigint RETURN x * 10"); + assertQuery("SELECT " + name + "(99)", "SELECT 990"); + + assertUpdate("CREATE OR REPLACE FUNCTION " + name + "(x integer) RETURNS bigint COMMENT 't42' RETURN x * 42"); + assertQuery("SELECT " + name + "(99)", "SELECT 4158"); + + assertQueryFails("SELECT " + name + "(2.9)", ".*Unexpected parameters.*"); + + assertUpdate("CREATE FUNCTION " + name + "(x double) RETURNS double COMMENT 't88' RETURN x * 8.8"); + + assertThat(query("SHOW FUNCTIONS")) + .result() + .skippingTypesCheck() + .containsAll(resultBuilder(getSession()) + .row(name, "bigint", "integer", "scalar", true, "t42") + .row(name, "double", "double", "scalar", true, "t88") + .build()); + + assertQuery("SELECT " + name + "(99)", "SELECT 4158"); + assertQuery("SELECT " + name + "(2.9)", "SELECT 25.52"); + + assertQueryFails("CREATE FUNCTION " + name + "(x int) RETURNS bigint RETURN x", "line 1:1: Function already exists"); + + assertQuery("SELECT " + name + "(99)", "SELECT 4158"); + assertQuery("SELECT " + name + "(2.9)", "SELECT 25.52"); + + assertUpdate("CREATE OR REPLACE FUNCTION " + name + "(x bigint) RETURNS bigint RETURN x * 23"); + assertUpdate("CREATE FUNCTION " + name2 + "(s varchar) RETURNS varchar RETURN 'Hello ' || s"); + + assertThat(query("SHOW FUNCTIONS")) + .result() + .skippingTypesCheck() + .containsAll(resultBuilder(getSession()) + .row(name, "bigint", "integer", "scalar", true, "t42") + .row(name, "bigint", "bigint", "scalar", true, "") + .row(name, "double", "double", "scalar", true, "t88") + .row(name2, "varchar", "varchar", "scalar", true, "") + .build()); + + assertQuery("SELECT " + name + "(99)", "SELECT 4158"); + assertQuery("SELECT " + name + "(cast(99 as bigint))", "SELECT 2277"); + assertQuery("SELECT " + name + "(2.9)", "SELECT 25.52"); + assertQuery("SELECT " + name2 + "('world')", "SELECT 'Hello world'"); + + assertQueryFails("DROP FUNCTION " + name + "(varchar)", "line 1:1: Function not found"); + assertUpdate("DROP FUNCTION " + name + "(z bigint)"); + assertUpdate("DROP FUNCTION " + name + "(double)"); + assertUpdate("DROP FUNCTION " + name + "(int)"); + assertQueryFails("DROP FUNCTION " + name + "(bigint)", "line 1:1: Function not found"); + assertUpdate("DROP FUNCTION IF EXISTS " + name + "(bigint)"); + assertUpdate("DROP FUNCTION " + name2 + "(varchar)"); + assertQueryFails("DROP FUNCTION " + name2 + "(varchar)", "line 1:1: Function not found"); + } + + private void renamePartitionResourcesOutsideTrino(String tableName, String partitionColumn, String regionKey) + { + String partitionName = format("%s=%s", partitionColumn, regionKey); + String partitionS3KeyPrefix = format("%s/%s/%s", HIVE_TEST_SCHEMA, tableName, partitionName); + String renamedPartitionSuffix = "CP"; + + // Copy whole partition to new location + MinioClient minioClient = hiveMinioDataLake.getMinioClient(); + minioClient.listObjects(bucketName, "") + .forEach(objectKey -> { + if (objectKey.startsWith(partitionS3KeyPrefix)) { + String fileName = objectKey.substring(objectKey.lastIndexOf('/')); + String destinationKey = partitionS3KeyPrefix + renamedPartitionSuffix + fileName; + minioClient.copyObject(bucketName, objectKey, bucketName, destinationKey); + } + }); + + // Delete old partition and update metadata to point to location of new copy + Table hiveTable = metastoreClient.getTable(HIVE_TEST_SCHEMA, tableName).orElseThrow(); + Partition partition = metastoreClient.getPartition(hiveTable, List.of(regionKey)).orElseThrow(); + Map> partitionStatistics = metastoreClient.getPartitionColumnStatistics( + HIVE_TEST_SCHEMA, + tableName, + ImmutableSet.of(partitionName), + partition.getColumns().stream().map(Column::getName).collect(toSet())); + + metastoreClient.dropPartition(HIVE_TEST_SCHEMA, tableName, List.of(regionKey), true); + metastoreClient.addPartitions(HIVE_TEST_SCHEMA, tableName, List.of( + new PartitionWithStatistics( + Partition.builder(partition) + .withStorage(builder -> builder.setLocation( + partition.getStorage().getLocation() + renamedPartitionSuffix)) + .build(), + partitionName, + new PartitionStatistics(getHiveBasicStatistics(partition.getParameters()), partitionStatistics.get(partitionName))))); + } + + protected void assertInsertFailure(String testTable, String expectedMessageRegExp) + { + assertInsertFailure(getSession(), testTable, expectedMessageRegExp); + } + + protected void assertInsertFailure(Session session, String testTable, String expectedMessageRegExp) + { + assertQueryFails( + session, + createInsertAsSelectFromTpchStatement(testTable), + expectedMessageRegExp); + } + + private String createInsertAsSelectFromTpchStatement(String testTable) + { + return format("INSERT INTO %s " + + "SELECT name, comment, nationkey, regionkey " + + "FROM tpch.tiny.nation", + testTable); + } + + protected String createInsertStatement(String testTable, List> data) + { + String values = data.stream() + .map(row -> String.join(", ", row)) + .collect(Collectors.joining("), (")); + return format("INSERT INTO %s VALUES (%s)", testTable, values); + } + + protected void assertOverwritePartition(String testTable) + { + computeActual(createInsertStatement( + testTable, + ImmutableList.of( + ImmutableList.of("'POLAND'", "'Test Data'", "25", "5"), + ImmutableList.of("'CZECH'", "'Test Data'", "26", "5")))); + query(format("SELECT name, comment, nationkey, regionkey FROM %s WHERE regionkey = 5", testTable)) + .assertThat() + .result() + .skippingTypesCheck() + .containsAll(resultBuilder(getSession()) + .row("POLAND", "Test Data", 25L, 5L) + .row("CZECH", "Test Data", 26L, 5L) + .build()); + + computeActual(createInsertStatement( + testTable, + ImmutableList.of( + ImmutableList.of("'POLAND'", "'Overwrite'", "25", "5")))); + query(format("SELECT name, comment, nationkey, regionkey FROM %s WHERE regionkey = 5", testTable)) + .assertThat() + .result() + .skippingTypesCheck() + .containsAll(resultBuilder(getSession()) + .row("POLAND", "Overwrite", 25L, 5L) + .build()); + computeActual(format("DROP TABLE %s", testTable)); + } + + protected String getRandomTestTableName() + { + return "nation_" + randomNameSuffix(); + } + + protected String getFullyQualifiedTestTableName() + { + return getFullyQualifiedTestTableName(getRandomTestTableName()); + } + + protected String getFullyQualifiedTestTableName(String tableName) + { + return getFullyQualifiedTestTableName(HIVE_TEST_SCHEMA, tableName); + } + + protected String getFullyQualifiedTestTableName(String schemaName, String tableName) + { + return "hive.%s.%s".formatted(schemaName, tableName); + } + + protected String getHiveTestTableName(String tableName) + { + return getHiveTestTableName(HIVE_TEST_SCHEMA, tableName); + } + + protected String getHiveTestTableName(String schemaName, String tableName) + { + return "%s.%s".formatted(schemaName, tableName); + } + + protected String getCreateTableStatement(String tableName, String... propertiesEntries) + { + return getCreateTableStatement(tableName, Arrays.asList(propertiesEntries)); + } + + protected String getCreateTableStatement(String tableName, List propertiesEntries) + { + return format( + "CREATE TABLE %s (" + + " name varchar(25), " + + " comment varchar(152), " + + " nationkey bigint, " + + " regionkey bigint) " + + (propertiesEntries.isEmpty() ? "" : propertiesEntries + .stream() + .collect(joining(",", "WITH (", ")"))), + tableName); + } + + protected void copyTpchNationToTable(String testTable) + { + computeActual(format("INSERT INTO " + testTable + " SELECT name, comment, nationkey, regionkey FROM tpch.tiny.nation")); + } + + private void testWriteWithFileSize(String testTable, int scaleFactorInThousands, long fileSizeRangeStart, long fileSizeRangeEnd) + { + String scaledColumnExpression = format("array_join(transform(sequence(1, %d), x-> array_join(repeat(comment, 1000), '')), '')", scaleFactorInThousands); + computeActual(format("INSERT INTO " + testTable + " SELECT %s, %s, regionkey FROM tpch.tiny.nation WHERE nationkey = 9", scaledColumnExpression, scaledColumnExpression)); + query(format("SELECT length(col1) FROM %s", testTable)) + .assertThat() + .result() + .skippingTypesCheck() + .containsAll(resultBuilder(getSession()) + .row(114L * scaleFactorInThousands * 1000) + .build()); + query(format("SELECT \"$file_size\" BETWEEN %d AND %d FROM %s", fileSizeRangeStart, fileSizeRangeEnd, testTable)) + .assertThat() + .result() + .skippingTypesCheck() + .containsAll(resultBuilder(getSession()) + .row(true) + .build()); + } + + private void addPartitions( + String sourceTableName, + String destinationExternalTableName, + List columnNames, + TupleDomain partitionsKeyFilter) + { + Optional> partitionNames = metastoreClient.getPartitionNamesByFilter(HIVE_TEST_SCHEMA, sourceTableName, columnNames, partitionsKeyFilter); + if (partitionNames.isEmpty()) { + // nothing to add + return; + } + Table table = metastoreClient.getTable(HIVE_TEST_SCHEMA, sourceTableName) + .orElseThrow(() -> new TableNotFoundException(new SchemaTableName(HIVE_TEST_SCHEMA, sourceTableName))); + Map> partitionsByNames = metastoreClient.getPartitionsByNames(table, partitionNames.get()); + + metastoreClient.addPartitions( + HIVE_TEST_SCHEMA, + destinationExternalTableName, + partitionsByNames.entrySet().stream() + .map(e -> new PartitionWithStatistics( + e.getValue() + .map(p -> Partition.builder(p).setTableName(destinationExternalTableName).build()) + .orElseThrow(), + e.getKey(), + PartitionStatistics.empty())) + .collect(toImmutableList())); + } + + private String getTableLocation(String tableName) + { + return (String) computeScalar("SELECT DISTINCT regexp_replace(\"$path\", '/[^/]*$', '') FROM " + tableName); + } + + @Test + public void testInsertOverwritePartitionedAndBucketedAcidTable() + { + String testTable = getFullyQualifiedTestTableName(); + computeActual(getCreateTableStatement( + testTable, + "partitioned_by=ARRAY['regionkey']", + "bucketed_by = ARRAY['nationkey']", + "bucket_count = 3", + "format = 'ORC'", + "transactional = true")); + assertInsertFailure( + testTable, + "Overwriting existing partition in transactional tables doesn't support DIRECT_TO_TARGET_EXISTING_DIRECTORY write mode"); + } +} 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 786108fc342a..a84652e6320a 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 @@ -22,7 +22,7 @@ import io.trino.metadata.QualifiedObjectName; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.tpcds.TpcdsPlugin; import io.trino.plugin.tpch.ColumnNaming; import io.trino.plugin.tpch.DecimalTypeMapping; 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 558a45059a5e..75efa817c54b 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 @@ -13,2264 +13,21 @@ */ 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.units.DataSize; -import io.trino.Session; -import io.trino.metastore.Column; -import io.trino.metastore.HiveColumnStatistics; -import io.trino.metastore.HiveMetastore; -import io.trino.metastore.Partition; -import io.trino.metastore.PartitionStatistics; -import io.trino.metastore.PartitionWithStatistics; -import io.trino.metastore.Table; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.containers.HiveHadoop; -import io.trino.plugin.hive.containers.HiveMinioDataLake; -import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; -import io.trino.plugin.hive.s3.S3HiveQueryRunner; -import io.trino.spi.connector.SchemaTableName; -import io.trino.spi.connector.TableNotFoundException; -import io.trino.spi.predicate.NullableValue; -import io.trino.spi.predicate.TupleDomain; -import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.QueryRunner; -import io.trino.testing.minio.MinioClient; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.ZoneId; -import java.time.temporal.TemporalUnit; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TimeZone; -import java.util.stream.Collectors; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.airlift.slice.Slices.utf8Slice; -import static io.airlift.units.DataSize.Unit.MEGABYTE; -import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; -import static io.trino.plugin.hive.metastore.MetastoreUtil.getHiveBasicStatistics; -import static io.trino.spi.type.VarcharType.VARCHAR; -import static io.trino.testing.MaterializedResult.resultBuilder; import static io.trino.testing.TestingNames.randomNameSuffix; -import static java.lang.String.format; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.time.temporal.ChronoUnit.DAYS; -import static java.time.temporal.ChronoUnit.MINUTES; -import static java.util.regex.Pattern.quote; -import static java.util.stream.Collectors.joining; -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.TestInstance.Lifecycle.PER_CLASS; @TestInstance(PER_CLASS) -public class TestHive3OnDataLake - extends AbstractTestQueryFramework +class TestHive3OnDataLake + extends BaseTestHiveOnDataLake { - private static final String HIVE_TEST_SCHEMA = "hive_datalake"; - private static final DataSize HIVE_S3_STREAMING_PART_SIZE = DataSize.of(5, MEGABYTE); - - private String bucketName; - private HiveMinioDataLake hiveMinioDataLake; - private HiveMetastore metastoreClient; - - @Override - protected QueryRunner createQueryRunner() - throws Exception - { - this.bucketName = "test-hive-insert-overwrite-" + randomNameSuffix(); - this.hiveMinioDataLake = closeAfterClass( - new HiveMinioDataLake(bucketName, HiveHadoop.HIVE3_IMAGE)); - this.hiveMinioDataLake.start(); - this.metastoreClient = new BridgingHiveMetastore( - testingThriftHiveMetastoreBuilder() - .metastoreClient(this.hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint()) - .build(this::closeAfterClass)); - return S3HiveQueryRunner.builder(hiveMinioDataLake) - .addExtraProperty("sql.path", "hive.functions") - .addExtraProperty("sql.default-function-catalog", "hive") - .addExtraProperty("sql.default-function-schema", "functions") - .setHiveProperties( - ImmutableMap.builder() - .put("hive.insert-existing-partitions-behavior", "OVERWRITE") - .put("hive.non-managed-table-writes-enabled", "true") - // Below are required to enable caching on metastore - .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("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()) - .build(); - } - - @BeforeAll - public void setUp() - { - computeActual(format( - "CREATE SCHEMA hive.%1$s WITH (location='s3a://%2$s/%1$s')", - HIVE_TEST_SCHEMA, - bucketName)); - computeActual("CREATE SCHEMA hive.functions"); - } - - @Test - public void testInsertOverwriteInTransaction() - { - String testTable = getFullyQualifiedTestTableName(); - computeActual(getCreateTableStatement(testTable, "partitioned_by=ARRAY['regionkey']")); - assertThatThrownBy( - () -> newTransaction() - .execute(getSession(), session -> { - getQueryRunner().execute(session, createInsertAsSelectFromTpchStatement(testTable)); - })) - .hasMessage("Overwriting existing partition in non auto commit context doesn't support DIRECT_TO_TARGET_EXISTING_DIRECTORY write mode"); - computeActual(format("DROP TABLE %s", testTable)); - } - - @Test - public void testInsertOverwriteNonPartitionedTable() - { - String testTable = getFullyQualifiedTestTableName(); - computeActual(getCreateTableStatement(testTable)); - assertInsertFailure( - testTable, - "Overwriting unpartitioned table not supported when writing directly to target directory"); - computeActual(format("DROP TABLE %s", testTable)); - } - - @Test - public void testInsertOverwriteNonPartitionedBucketedTable() - { - String testTable = getFullyQualifiedTestTableName(); - computeActual(getCreateTableStatement( - testTable, - "bucketed_by = ARRAY['nationkey']", - "bucket_count = 3")); - assertInsertFailure( - testTable, - "Overwriting unpartitioned table not supported when writing directly to target directory"); - computeActual(format("DROP TABLE %s", testTable)); - } - - @Test - public void testInsertOverwritePartitionedTable() - { - String testTable = getFullyQualifiedTestTableName(); - computeActual(getCreateTableStatement( - testTable, - "partitioned_by=ARRAY['regionkey']")); - copyTpchNationToTable(testTable); - assertOverwritePartition(testTable); - } - - @Test - public void testInsertOverwritePartitionedAndBucketedTable() - { - String testTable = getFullyQualifiedTestTableName(); - computeActual(getCreateTableStatement( - testTable, - "partitioned_by=ARRAY['regionkey']", - "bucketed_by = ARRAY['nationkey']", - "bucket_count = 3")); - copyTpchNationToTable(testTable); - assertOverwritePartition(testTable); - } - - @Test - public void testInsertOverwritePartitionedAndBucketedExternalTable() - { - String testTable = getFullyQualifiedTestTableName(); - // Store table data in data lake bucket - computeActual(getCreateTableStatement( - testTable, - "partitioned_by=ARRAY['regionkey']", - "bucketed_by = ARRAY['nationkey']", - "bucket_count = 3")); - copyTpchNationToTable(testTable); - - // Map this table as external table - String externalTableName = testTable + "_ext"; - computeActual(getCreateTableStatement( - externalTableName, - "partitioned_by=ARRAY['regionkey']", - "bucketed_by = ARRAY['nationkey']", - "bucket_count = 3", - format("external_location = 's3a://%s/%s/%s/'", this.bucketName, HIVE_TEST_SCHEMA, testTable))); - copyTpchNationToTable(testTable); - assertOverwritePartition(externalTableName); - } - - @Test - public void testSyncPartitionOnBucketRoot() - { - String tableName = "test_sync_partition_on_bucket_root_" + randomNameSuffix(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - hiveMinioDataLake.getMinioClient().putObject( - bucketName, - "hello\u0001world\nbye\u0001world".getBytes(UTF_8), - "part_key=part_val/data.txt"); - - assertUpdate("CREATE TABLE " + fullyQualifiedTestTableName + "(" + - " a varchar," + - " b varchar," + - " part_key varchar)" + - "WITH (" + - " external_location='s3://" + bucketName + "/'," + - " partitioned_by=ARRAY['part_key']," + - " format='TEXTFILE'" + - ")"); - - getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'ADD')"); - - assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, "VALUES ('hello', 'world', 'part_val'), ('bye', 'world', 'part_val')"); - - assertUpdate("DROP TABLE " + fullyQualifiedTestTableName); - } - - @Test - public void testSyncPartitionCaseSensitivePathVariation() - { - String tableName = "test_sync_partition_case_variation_" + randomNameSuffix(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - String tableLocation = format("s3://%s/%s/%s/", bucketName, HIVE_TEST_SCHEMA, tableName); - - hiveMinioDataLake.getMinioClient().putObject( - bucketName, - "Trino\u0001rocks".getBytes(UTF_8), - HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=part_val/data.txt"); - - assertUpdate("CREATE TABLE " + fullyQualifiedTestTableName + "(" + - " a varchar," + - " b varchar," + - " part_key varchar)" + - "WITH (" + - " external_location='" + tableLocation + "'," + - " partitioned_by=ARRAY['part_key']," + - " format='TEXTFILE'" + - ")"); - - getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'ADD')"); - assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, "VALUES ('Trino', 'rocks', 'part_val')"); - - // Move the data to a location where the partition path differs only in case - hiveMinioDataLake.getMinioClient().removeObject(bucketName, HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=part_val/data.txt"); - hiveMinioDataLake.getMinioClient().putObject( - bucketName, - "Trino\u0001rocks".getBytes(UTF_8), - HIVE_TEST_SCHEMA + "/" + tableName + "/PART_KEY=part_val/data.txt"); - - getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'FULL', case_sensitive => false)"); - assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, "VALUES ('Trino', 'rocks', 'part_val')"); - - // Verify that syncing again the partition metadata has no negative effect (e.g. drop the partition) - getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'FULL', case_sensitive => false)"); - assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, "VALUES ('Trino', 'rocks', 'part_val')"); - - assertUpdate("DROP TABLE " + fullyQualifiedTestTableName); - } - - @Test - public void testSyncPartitionSpecialCharacters() - { - String tableName = "test_sync_partition_special_characters_" + randomNameSuffix(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - String tableLocation = format("s3://%s/%s/%s/", bucketName, HIVE_TEST_SCHEMA, tableName); - - hiveMinioDataLake.getMinioClient().putObject( - bucketName, - "Trino\u0001rocks\u0001hyphens".getBytes(UTF_8), - HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with-hyphen/data.txt"); - hiveMinioDataLake.getMinioClient().putObject( - bucketName, - "Trino\u0001rocks\u0001dots".getBytes(UTF_8), - HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with.dot/data.txt"); - hiveMinioDataLake.getMinioClient().putObject( - bucketName, - "Trino\u0001rocks\u0001colons".getBytes(UTF_8), - HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with%3Acolon/data.txt"); - hiveMinioDataLake.getMinioClient().putObject( - bucketName, - "Trino\u0001rocks\u0001slashes".getBytes(UTF_8), - HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with%2Fslash/data.txt"); - hiveMinioDataLake.getMinioClient().putObject( - bucketName, - "Trino\u0001rocks\u0001backslashes".getBytes(UTF_8), - HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with%5Cbackslash/data.txt"); - hiveMinioDataLake.getMinioClient().putObject( - bucketName, - "Trino\u0001rocks\u0001percents".getBytes(UTF_8), - HIVE_TEST_SCHEMA + "/" + tableName + "/part_key=with%25percent/data.txt"); - - assertUpdate("CREATE TABLE " + fullyQualifiedTestTableName + "(" + - " a varchar," + - " b varchar," + - " c varchar," + - " part_key varchar)" + - "WITH (" + - " external_location='" + tableLocation + "'," + - " partitioned_by=ARRAY['part_key']," + - " format='TEXTFILE'" + - ")"); - - getQueryRunner().execute("CALL system.sync_partition_metadata(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "', mode => 'ADD')"); - assertQuery( - "SELECT * FROM " + fullyQualifiedTestTableName, - """ - VALUES - ('Trino', 'rocks', 'hyphens', 'with-hyphen'), - ('Trino', 'rocks', 'dots', 'with.dot'), - ('Trino', 'rocks', 'colons', 'with:colon'), - ('Trino', 'rocks', 'slashes', 'with/slash'), - ('Trino', 'rocks', 'backslashes', 'with\\backslash'), - ('Trino', 'rocks', 'percents', 'with%percent') - """); - - assertUpdate("DROP TABLE " + fullyQualifiedTestTableName); - } - - @Test - public void testFlushPartitionCache() - { - String tableName = "nation_" + randomNameSuffix(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - String partitionColumn = "regionkey"; - - testFlushPartitionCache( - tableName, - fullyQualifiedTestTableName, - partitionColumn, - format( - "CALL system.flush_metadata_cache(schema_name => '%s', table_name => '%s', partition_columns => ARRAY['%s'], partition_values => ARRAY['0'])", - HIVE_TEST_SCHEMA, - tableName, - partitionColumn)); - } - - private void testFlushPartitionCache(String tableName, String fullyQualifiedTestTableName, String partitionColumn, String flushCacheProcedureSql) - { - // Create table with partition on regionkey - computeActual(getCreateTableStatement( - fullyQualifiedTestTableName, - format("partitioned_by=ARRAY['%s']", partitionColumn))); - copyTpchNationToTable(fullyQualifiedTestTableName); - - String queryUsingPartitionCacheTemplate = "SELECT name FROM %s WHERE %s=%s"; - String partitionValue1 = "0"; - String queryUsingPartitionCacheForValue1 = format(queryUsingPartitionCacheTemplate, fullyQualifiedTestTableName, partitionColumn, partitionValue1); - String expectedQueryResultForValue1 = "VALUES 'ALGERIA', 'MOROCCO', 'MOZAMBIQUE', 'ETHIOPIA', 'KENYA'"; - String partitionValue2 = "1"; - String queryUsingPartitionCacheForValue2 = format(queryUsingPartitionCacheTemplate, fullyQualifiedTestTableName, partitionColumn, partitionValue2); - String expectedQueryResultForValue2 = "VALUES 'ARGENTINA', 'BRAZIL', 'CANADA', 'PERU', 'UNITED STATES'"; - - // Fill partition cache and check we got expected results - assertQuery(queryUsingPartitionCacheForValue1, expectedQueryResultForValue1); - assertQuery(queryUsingPartitionCacheForValue2, expectedQueryResultForValue2); - - // Copy partition to new location and update metadata outside Trino - renamePartitionResourcesOutsideTrino(tableName, partitionColumn, partitionValue1); - renamePartitionResourcesOutsideTrino(tableName, partitionColumn, partitionValue2); - - // Should return 0 rows as we moved partition and cache is outdated. We use nonexistent partition - assertQueryReturnsEmptyResult(queryUsingPartitionCacheForValue1); - assertQueryReturnsEmptyResult(queryUsingPartitionCacheForValue2); - - // Refresh cache - getQueryRunner().execute(flushCacheProcedureSql); - - // Should return expected rows as we refresh cache - assertQuery(queryUsingPartitionCacheForValue1, expectedQueryResultForValue1); - // Should return 0 rows as we left cache untouched - assertQueryReturnsEmptyResult(queryUsingPartitionCacheForValue2); - - // Refresh cache for schema_name => 'dummy_schema', table_name => 'dummy_table' - getQueryRunner().execute(format( - "CALL system.flush_metadata_cache(schema_name => '%s', table_name => '%s')", - HIVE_TEST_SCHEMA, - tableName)); - - // Should return expected rows for all partitions - assertQuery(queryUsingPartitionCacheForValue1, expectedQueryResultForValue1); - assertQuery(queryUsingPartitionCacheForValue2, expectedQueryResultForValue2); - - computeActual(format("DROP TABLE %s", fullyQualifiedTestTableName)); - } - - @Test - public void testWriteDifferentSizes() - { - String testTable = getFullyQualifiedTestTableName(); - computeActual(format( - "CREATE TABLE %s (" + - " col1 varchar, " + - " col2 varchar, " + - " regionkey bigint) " + - " WITH (partitioned_by=ARRAY['regionkey'])", - testTable)); - - long partSizeInBytes = HIVE_S3_STREAMING_PART_SIZE.toBytes(); - - // Exercise different code paths of Hive S3 streaming upload, with upload part size 5MB: - // 1. fileSize <= 5MB (direct upload) - testWriteWithFileSize(testTable, 50, 0, partSizeInBytes); - - // 2. 5MB < fileSize <= 10MB (upload in two parts) - testWriteWithFileSize(testTable, 100, partSizeInBytes + 1, partSizeInBytes * 2); - - // 3. fileSize > 10MB (upload in three or more parts) - testWriteWithFileSize(testTable, 150, partSizeInBytes * 2 + 1, partSizeInBytes * 3); - - computeActual(format("DROP TABLE %s", testTable)); - } - - @Test - public void testEnumPartitionProjectionOnVarcharColumnWithWhitespace() - { - String tableName = "nation_" + randomNameSuffix(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " (" + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " \"short name\" varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short name'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short name\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short name\\.values[ |]+PL1,CZ1[ |]+"); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL2'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ2'")))); - - assertQuery( - format("SELECT * FROM %s", getFullyQualifiedTestTableName("\"" + tableName + "$partitions\"")), - "VALUES 'PL1', 'CZ1'"); - - assertQuery( - format("SELECT name FROM %s WHERE \"short name\"='PL1'", fullyQualifiedTestTableName), - "VALUES 'POLAND_1'"); - - // No results should be returned as Partition Projection will not project partitions for this value - assertQueryReturnsEmptyResult( - format("SELECT name FROM %s WHERE \"short name\"='PL2'", fullyQualifiedTestTableName)); - - assertQuery( - format("SELECT name FROM %s WHERE \"short name\"='PL1' OR \"short name\"='CZ1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('CZECH_1')"); - - // Only POLAND_1 row will be returned as other value is outside of projection - assertQuery( - format("SELECT name FROM %s WHERE \"short name\"='PL1' OR \"short name\"='CZ2'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1')"); - - // All values within projection range will be returned - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('CZECH_1')"); - } - - @Test - public void testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplateCreatedOnTrino() - { - // It's important to mix case here to detect if we properly handle rewriting - // properties between Trino and Hive (e.g for Partition Projection) - String schemaName = "Hive_Datalake_MixedCase"; - String tableName = getRandomTestTableName(); - - // We create new schema to include mixed case location path and create such keys in Object Store - computeActual("CREATE SCHEMA hive.%1$s WITH (location='s3a://%2$s/%1$s')".formatted(schemaName, bucketName)); - - String storageFormat = format( - "s3a://%s/%s/%s/short_name1=${short_name1}/short_name2=${short_name2}/", - this.bucketName, - schemaName, - tableName); - computeActual( - "CREATE TABLE " + getFullyQualifiedTestTableName(schemaName, tableName) + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL2', 'CZ2'] " + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true, " + - " partition_projection_location_template='" + storageFormat + "' " + - ")"); - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(schemaName, tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+storage\\.location\\.template[ |]+" + quote(storageFormat) + "[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.values[ |]+PL2,CZ2[ |]+"); - testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplate(schemaName, tableName); - } - - @Test - public void testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplateCreatedOnHive() - { - String tableName = getRandomTestTableName(); - String storageFormat = format( - "'s3a://%s/%s/%s/short_name1=${short_name1}/short_name2=${short_name2}/'", - this.bucketName, - HIVE_TEST_SCHEMA, - tableName); - hiveMinioDataLake.getHiveHadoop().runOnHive( - "CREATE TABLE " + getHiveTestTableName(tableName) + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint " + - ") PARTITIONED BY (" + - " short_name1 varchar(152), " + - " short_name2 varchar(152)" + - ") " + - "TBLPROPERTIES ( " + - " 'projection.enabled'='true', " + - " 'storage.location.template'=" + storageFormat + ", " + - " 'projection.short_name1.type'='enum', " + - " 'projection.short_name1.values'='PL1,CZ1', " + - " 'projection.short_name2.type'='enum', " + - " 'projection.short_name2.values'='PL2,CZ2' " + - ")"); - testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplate(HIVE_TEST_SCHEMA, tableName); - } - - private void testEnumPartitionProjectionOnVarcharColumnWithStorageLocationTemplate(String schemaName, String tableName) - { - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(schemaName, tableName); - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'PL2'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'CZ2'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'PL2'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'CZ2'")))); - - assertQuery( - format("SELECT * FROM %s", getFullyQualifiedTestTableName(schemaName, "\"" + tableName + "$partitions\"")), - "VALUES ('PL1','PL2'), ('PL1','CZ2'), ('CZ1','PL2'), ('CZ1','CZ2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='CZ2'", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testEnumPartitionProjectionOnVarcharColumn() - { - String tableName = getRandomTestTableName(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL2', 'CZ2']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.values[ |]+PL2,CZ2[ |]+"); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'PL2'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'CZ2'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'PL2'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'CZ2'")))); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='CZ2'", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='CZ2' OR short_name2='PL2' )", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlignCreatedOnTrino() - { - String tableName = getRandomTestTableName(); - computeActual( - "CREATE TABLE " + getFullyQualifiedTestTableName(tableName) + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 varchar(152) WITH (" + - " partition_projection_type='integer', " + - " partition_projection_range=ARRAY['1', '4'], " + - " partition_projection_digits=3" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+integer[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+1,4[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.digits[ |]+3[ |]+"); - testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlign(tableName); - } - - @Test - public void testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlignCreatedOnHive() - { - String tableName = "nation_" + randomNameSuffix(); - hiveMinioDataLake.getHiveHadoop().runOnHive( - "CREATE TABLE " + getHiveTestTableName(tableName) + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint " + - ") " + - "PARTITIONED BY ( " + - " short_name1 varchar(152), " + - " short_name2 varchar(152)" + - ") " + - "TBLPROPERTIES " + - "( " + - " 'projection.enabled'='true', " + - " 'projection.short_name1.type'='enum', " + - " 'projection.short_name1.values'='PL1,CZ1', " + - " 'projection.short_name2.type'='integer', " + - " 'projection.short_name2.range'='1,4', " + - " 'projection.short_name2.digits'='3'" + - ")"); - testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlign(tableName); - } - - private void testIntegerPartitionProjectionOnVarcharColumnWithDigitsAlign(String tableName) - { - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'001'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'002'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'003'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'004'")))); - - assertQuery( - format("SELECT * FROM %s", getFullyQualifiedTestTableName("\"" + tableName + "$partitions\"")), - "VALUES ('PL1','001'), ('PL1','002'), ('PL1','003'), ('PL1','004')," + - "('CZ1','001'), ('CZ1','002'), ('CZ1','003'), ('CZ1','004')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='002'", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='002' OR short_name2='001' )", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testIntegerPartitionProjectionOnIntegerColumnWithInterval() - { - String tableName = getRandomTestTableName(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 integer WITH (" + - " partition_projection_type='integer', " + - " partition_projection_range=ARRAY['0', '10'], " + - " partition_projection_interval=3" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+integer[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+0,10[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+3[ |]+"); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "0"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "3"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "6"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "9")))); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2=3", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2=3 OR short_name2=0 )", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testIntegerPartitionProjectionOnIntegerColumnWithDefaults() - { - String tableName = getRandomTestTableName(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 integer WITH (" + - " partition_projection_type='integer', " + - " partition_projection_range=ARRAY['1', '4']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+integer[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+1,4[ |]+"); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "1"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "2"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "3"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "4")))); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2=2", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2=2 OR short_name2=1 )", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testDatePartitionProjectionOnDateColumnWithDefaults() - { - String tableName = "nation_" + randomNameSuffix(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 date WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd', " + - " partition_projection_range=ARRAY['2001-1-22', '2001-1-25']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+yyyy-MM-dd[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+2001-1-22,2001-1-25[ |]+"); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "DATE '2001-1-22'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "DATE '2001-1-23'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "DATE '2001-1-24'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "DATE '2001-1-25'"), - ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "DATE '2001-1-26'")))); - - assertQuery( - format("SELECT * FROM %s", getFullyQualifiedTestTableName("\"" + tableName + "$partitions\"")), - "VALUES ('PL1','2001-1-22'), ('PL1','2001-1-23'), ('PL1','2001-1-24'), ('PL1','2001-1-25')," + - "('CZ1','2001-1-22'), ('CZ1','2001-1-23'), ('CZ1','2001-1-24'), ('CZ1','2001-1-25')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2=(DATE '2001-1-23')", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2=(DATE '2001-1-23') OR short_name2=(DATE '2001-1-22') )", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 > DATE '2001-1-23'", fullyQualifiedTestTableName), - "VALUES ('CZECH_1'), ('CZECH_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 >= DATE '2001-1-23' AND short_name2 <= DATE '2001-1-25'", fullyQualifiedTestTableName), - "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testDatePartitionProjectionOnTimestampColumnWithInterval() - { - String tableName = getRandomTestTableName(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 timestamp WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd HH:mm:ss', " + - " partition_projection_range=ARRAY['2001-1-22 00:00:00', '2001-1-22 00:00:06'], " + - " partition_projection_interval=2, " + - " partition_projection_interval_unit='SECONDS'" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+yyyy-MM-dd HH:mm:ss[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+2001-1-22 00:00:00,2001-1-22 00:00:06[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+2[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.interval\\.unit[ |]+seconds[ |]+"); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "TIMESTAMP '2001-1-22 00:00:00'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "TIMESTAMP '2001-1-22 00:00:02'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "TIMESTAMP '2001-1-22 00:00:04'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "TIMESTAMP '2001-1-22 00:00:06'"), - ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "TIMESTAMP '2001-1-22 00:00:08'")))); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2=(TIMESTAMP '2001-1-22 00:00:02')", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2=(TIMESTAMP '2001-1-22 00:00:00') OR short_name2=(TIMESTAMP '2001-1-22 00:00:02') )", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 > TIMESTAMP '2001-1-22 00:00:02'", fullyQualifiedTestTableName), - "VALUES ('CZECH_1'), ('CZECH_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 >= TIMESTAMP '2001-1-22 00:00:02' AND short_name2 <= TIMESTAMP '2001-1-22 00:00:06'", fullyQualifiedTestTableName), - "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testDatePartitionProjectionOnTimestampColumnWithIntervalExpressionCreatedOnTrino() - { - String tableName = getRandomTestTableName(); - String dateProjectionFormat = "yyyy-MM-dd HH:mm:ss"; - computeActual( - "CREATE TABLE " + getFullyQualifiedTestTableName(tableName) + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 timestamp WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='" + dateProjectionFormat + "', " + - // We set range to -5 minutes to NOW in order to be sure it will grab all test dates - // which range is -4 minutes till now. Also, we have to consider max no. of partitions 1k - " partition_projection_range=ARRAY['NOW-5MINUTES', 'NOW'], " + - " partition_projection_interval=1, " + - " partition_projection_interval_unit='SECONDS'" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+" + quote(dateProjectionFormat) + "[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+NOW-5MINUTES,NOW[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.interval\\.unit[ |]+seconds[ |]+"); - testDatePartitionProjectionOnTimestampColumnWithIntervalExpression(tableName, dateProjectionFormat); - } - - @Test - public void testDatePartitionProjectionOnTimestampColumnWithIntervalExpressionCreatedOnHive() - { - String tableName = getRandomTestTableName(); - String dateProjectionFormat = "yyyy-MM-dd HH:mm:ss"; - hiveMinioDataLake.getHiveHadoop().runOnHive( - "CREATE TABLE " + getHiveTestTableName(tableName) + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint " + - ") " + - "PARTITIONED BY (" + - " short_name1 varchar(152), " + - " short_name2 timestamp " + - ") " + - "TBLPROPERTIES ( " + - " 'projection.enabled'='true', " + - " 'projection.short_name1.type'='enum', " + - " 'projection.short_name1.values'='PL1,CZ1', " + - " 'projection.short_name2.type'='date', " + - " 'projection.short_name2.format'='" + dateProjectionFormat + "', " + - // We set range to -5 minutes to NOW in order to be sure it will grab all test dates - // which range is -4 minutes till now. Also, we have to consider max no. of partitions 1k - " 'projection.short_name2.range'='NOW-5MINUTES,NOW', " + - " 'projection.short_name2.interval'='1', " + - " 'projection.short_name2.interval.unit'='SECONDS'" + - ")"); - testDatePartitionProjectionOnTimestampColumnWithIntervalExpression(tableName, dateProjectionFormat); - } - - private void testDatePartitionProjectionOnTimestampColumnWithIntervalExpression(String tableName, String dateProjectionFormat) - { - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - Instant dayToday = Instant.now(); - DateFormat dateFormat = new SimpleDateFormat(dateProjectionFormat); - dateFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of("UTC"))); - String minutesNowFormatted = moveDate(dateFormat, dayToday, MINUTES, 0); - String minutes1AgoFormatter = moveDate(dateFormat, dayToday, MINUTES, -1); - String minutes2AgoFormatted = moveDate(dateFormat, dayToday, MINUTES, -2); - String minutes3AgoFormatted = moveDate(dateFormat, dayToday, MINUTES, -3); - String minutes4AgoFormatted = moveDate(dateFormat, dayToday, MINUTES, -4); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "TIMESTAMP '" + minutesNowFormatted + "'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "TIMESTAMP '" + minutes1AgoFormatter + "'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "TIMESTAMP '" + minutes2AgoFormatted + "'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "TIMESTAMP '" + minutes3AgoFormatted + "'"), - ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "TIMESTAMP '" + minutes4AgoFormatted + "'")))); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 > ( TIMESTAMP '%s' ) AND short_name2 <= ( TIMESTAMP '%s' )", fullyQualifiedTestTableName, minutes4AgoFormatted, minutes1AgoFormatter), - "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testDatePartitionProjectionOnVarcharColumnWithHoursInterval() - { - String tableName = getRandomTestTableName(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd HH', " + - " partition_projection_range=ARRAY['2001-01-22 00', '2001-01-22 06'], " + - " partition_projection_interval=2, " + - " partition_projection_interval_unit='HOURS'" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+yyyy-MM-dd HH[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+2001-01-22 00,2001-01-22 06[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+2[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.interval\\.unit[ |]+hours[ |]+"); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'2001-01-22 00'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'2001-01-22 02'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'2001-01-22 04'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'2001-01-22 06'"), - ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "'2001-01-22 08'")))); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='2001-01-22 02'", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='2001-01-22 00' OR short_name2='2001-01-22 02' )", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 > '2001-01-22 02'", fullyQualifiedTestTableName), - "VALUES ('CZECH_1'), ('CZECH_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 >= '2001-01-22 02' AND short_name2 <= '2001-01-22 06'", fullyQualifiedTestTableName), - "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testDatePartitionProjectionOnVarcharColumnWithDaysInterval() - { - String tableName = getRandomTestTableName(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd', " + - " partition_projection_range=ARRAY['2001-01-01', '2001-01-07'], " + - " partition_projection_interval=2, " + - " partition_projection_interval_unit='DAYS'" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+yyyy-MM-dd[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+2001-01-01,2001-01-07[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.interval[ |]+2[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.interval\\.unit[ |]+days[ |]+"); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'2001-01-01'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'2001-01-03'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'2001-01-05'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'2001-01-07'"), - ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "'2001-01-09'")))); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='2001-01-03'", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='2001-01-01' OR short_name2='2001-01-03' )", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 > '2001-01-03'", fullyQualifiedTestTableName), - "VALUES ('CZECH_1'), ('CZECH_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 >= '2001-01-03' AND short_name2 <= '2001-01-07'", fullyQualifiedTestTableName), - "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - @Test - public void testDatePartitionProjectionOnVarcharColumnWithIntervalExpression() - { - String tableName = getRandomTestTableName(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - String dateProjectionFormat = "yyyy-MM-dd"; - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='" + dateProjectionFormat + "', " + - " partition_projection_range=ARRAY['NOW-3DAYS', 'NOW']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+date[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.format[ |]+" + quote(dateProjectionFormat) + "[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.range[ |]+NOW-3DAYS,NOW[ |]+"); - - Instant dayToday = Instant.now(); - DateFormat dateFormat = new SimpleDateFormat(dateProjectionFormat); - dateFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of("UTC"))); - String dayTodayFormatted = moveDate(dateFormat, dayToday, DAYS, 0); - String day1AgoFormatter = moveDate(dateFormat, dayToday, DAYS, -1); - String day2AgoFormatted = moveDate(dateFormat, dayToday, DAYS, -2); - String day3AgoFormatted = moveDate(dateFormat, dayToday, DAYS, -3); - String day4AgoFormatted = moveDate(dateFormat, dayToday, DAYS, -4); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'" + dayTodayFormatted + "'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'" + day1AgoFormatter + "'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'" + day2AgoFormatted + "'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'" + day3AgoFormatted + "'"), - ImmutableList.of("'CZECH_3'", "'Comment'", "4", "5", "'CZ1'", "'" + day4AgoFormatted + "'")))); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='%s'", fullyQualifiedTestTableName, day1AgoFormatter), - "VALUES 'POLAND_2'"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='%s' OR short_name2='%s' )", fullyQualifiedTestTableName, dayTodayFormatted, day1AgoFormatter), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 > '%s'", fullyQualifiedTestTableName, day2AgoFormatted), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name2 >= '%s' AND short_name2 <= '%s'", fullyQualifiedTestTableName, day4AgoFormatted, day1AgoFormatter), - "VALUES ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2')"); - - assertQuery( - format("SELECT name FROM %s", fullyQualifiedTestTableName), - "VALUES ('POLAND_1'), ('POLAND_2'), ('CZECH_1'), ('CZECH_2')"); - } - - private String moveDate(DateFormat format, Instant today, TemporalUnit unit, int move) - { - return format.format(new Date(today.plus(move, unit).toEpochMilli())); - } - - @Test - public void testDatePartitionProjectionFormatTextWillNotCauseIntervalRequirement() - { - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='''start''yyyy-MM-dd''end''''s''', " + - " partition_projection_range=ARRAY['start2001-01-01end''s', 'start2001-01-07end''s'] " + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'], " + - " partition_projection_enabled=true " + - ")"); - } - - @Test - public void testInjectedPartitionProjectionOnVarcharColumn() - { - String tableName = getRandomTestTableName(); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - computeActual( - "CREATE TABLE " + fullyQualifiedTestTableName + " ( " + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint, " + - " short_name1 varchar(152) WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 varchar(152) WITH (" + - " partition_projection_type='injected'" + - " ) " + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")"); - - assertThat( - hiveMinioDataLake.getHiveHadoop() - .runOnHive("SHOW TBLPROPERTIES " + getHiveTestTableName(tableName))) - .containsPattern("[ |]+projection\\.enabled[ |]+true[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.type[ |]+enum[ |]+") - .containsPattern("[ |]+projection\\.short_name1\\.values[ |]+PL1,CZ1[ |]+") - .containsPattern("[ |]+projection\\.short_name2\\.type[ |]+injected[ |]+"); - - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'Comment'", "0", "5", "'PL1'", "'001'"), - ImmutableList.of("'POLAND_2'", "'Comment'", "1", "5", "'PL1'", "'002'"), - ImmutableList.of("'CZECH_1'", "'Comment'", "2", "5", "'CZ1'", "'003'"), - ImmutableList.of("'CZECH_2'", "'Comment'", "3", "5", "'CZ1'", "'004'")))); - - assertQuery( - format("SELECT name FROM %s WHERE short_name1='PL1' AND short_name2='002'", fullyQualifiedTestTableName), - "VALUES 'POLAND_2'"); - - assertThatThrownBy( - () -> getQueryRunner().execute( - format("SELECT name FROM %s WHERE short_name1='PL1' AND ( short_name2='002' OR short_name2='001' )", fullyQualifiedTestTableName))) - .hasMessage("Column projection for column 'short_name2' failed. Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); - - assertThatThrownBy( - () -> getQueryRunner().execute( - format("SELECT name FROM %s", fullyQualifiedTestTableName))) - .hasMessage("Column projection for column 'short_name2' failed. Injected projection requires single predicate for it's column in where clause"); - - assertThatThrownBy( - () -> getQueryRunner().execute( - format("SELECT name FROM %s WHERE short_name1='PL1'", fullyQualifiedTestTableName))) - .hasMessage("Column projection for column 'short_name2' failed. Injected projection requires single predicate for it's column in where clause"); - } - - @Test - public void testPartitionProjectionInvalidTableProperties() - { - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar " + - ") WITH ( " + - " partition_projection_enabled=true " + - ")")) - .hasMessage("Partition projection cannot be enabled on a table that is not partitioned"); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar WITH ( " + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1']" + - " ), " + - " short_name1 varchar " + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'], " + - " partition_projection_enabled=true " + - ")")) - .hasMessage("Partition projection cannot be defined for non-partition column: 'name'"); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH ( " + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1']" + - " ), " + - " short_name2 varchar " + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")")) - .hasMessage("Column projection for column 'short_name2' failed. Projection type property missing"); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " ), " + - " short_name2 varchar WITH (" + - " partition_projection_type='injected' " + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true, " + - " partition_projection_location_template='s3a://dummy/short_name1=${short_name1}/'" + - ")")) - .hasMessage("Partition projection location template: s3a://dummy/short_name1=${short_name1}/ " + - "is missing partition column: 'short_name2' placeholder"); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='integer', " + - " partition_projection_range=ARRAY['1', '2', '3']" + - " ), " + - " short_name2 varchar WITH (" + - " partition_projection_type='enum', " + - " partition_projection_values=ARRAY['PL1', 'CZ1'] " + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1', 'short_name2'], " + - " partition_projection_enabled=true " + - ")")) - .hasMessage("Column projection for column 'short_name1' failed. Property: 'partition_projection_range' needs to be list of 2 integers"); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_values=ARRAY['2001-01-01', '2001-01-02']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'], " + - " partition_projection_enabled=true " + - ")")) - .hasMessage("Column projection for column 'short_name1' failed. Missing required property: 'partition_projection_format'"); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd HH', " + - " partition_projection_range=ARRAY['2001-01-01', '2001-01-02']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'], " + - " 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\""); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd', " + - " partition_projection_range=ARRAY['NOW*3DAYS', '2001-01-02']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'], " + - " 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\""); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd', " + - " partition_projection_range=ARRAY['2001-01-02', '2001-01-01']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'], " + - " 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"); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd', " + - " partition_projection_range=ARRAY['2001-01-01', '2001-01-02'], " + - " partition_projection_interval_unit='Decades'" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'], " + - " partition_projection_enabled=true " + - ")")) - .hasMessage("Column projection for column 'short_name1' failed. Property: 'partition_projection_interval_unit' value 'Decades' is invalid. " + - "Available options: [Days, Hours, Minutes, Seconds]"); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd HH', " + - " partition_projection_range=ARRAY['2001-01-01 10', '2001-01-02 10']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'], " + - " partition_projection_enabled=true " + - ")")) - .hasMessage("Column projection for column 'short_name1' failed. Property: 'partition_projection_interval_unit' " + - "needs to be set when provided 'partition_projection_format' is less that single-day precision. " + - "Interval defaults to 1 day or 1 month, respectively. Otherwise, interval is required"); - - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd', " + - " partition_projection_range=ARRAY['2001-01-01 10', '2001-01-02 10']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'] " + - ")")) - .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. - assertThatThrownBy(() -> getQueryRunner().execute( - "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + - " name varchar, " + - " short_name1 varchar WITH (" + - " partition_projection_type='date', " + - " partition_projection_format='yyyy-MM-dd HH', " + - " partition_projection_range=ARRAY['2001-01-01 10', '2001-01-02 10']" + - " )" + - ") WITH ( " + - " partitioned_by=ARRAY['short_name1'], " + - " 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' " + - "needs to be set when provided 'partition_projection_format' is less that single-day precision. " + - "Interval defaults to 1 day or 1 month, respectively. Otherwise, interval is required"); - } - - @Test - public void testPartitionProjectionIgnore() - { - String tableName = "nation_" + randomNameSuffix(); - String hiveTestTableName = getHiveTestTableName(tableName); - String fullyQualifiedTestTableName = getFullyQualifiedTestTableName(tableName); - - // Create corrupted configuration - hiveMinioDataLake.getHiveHadoop().runOnHive( - "CREATE TABLE " + hiveTestTableName + " ( " + - " name varchar(25) " + - ") PARTITIONED BY (" + - " date_time varchar(152) " + - ") " + - "TBLPROPERTIES ( " + - " 'projection.enabled'='true', " + - " 'projection.date_time.type'='date', " + - " 'projection.date_time.format'='yyyy-MM-dd HH', " + - " 'projection.date_time.range'='2001-01-01,2001-01-02' " + - ")"); - - // 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\""); - - // Append kill switch table property to ignore Partition Projection properties - hiveMinioDataLake.getHiveHadoop().runOnHive( - "ALTER TABLE " + hiveTestTableName + " SET TBLPROPERTIES ( 'trino.partition_projection.ignore'='TRUE' )"); - // Flush cache to get new definition - computeActual("CALL system.flush_metadata_cache(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + tableName + "')"); - - // Verify query execution works - computeActual(createInsertStatement( - fullyQualifiedTestTableName, - ImmutableList.of( - ImmutableList.of("'POLAND_1'", "'2022-02-01 12'"), - ImmutableList.of("'POLAND_2'", "'2022-02-01 12'"), - ImmutableList.of("'CZECH_1'", "'2022-02-01 13'"), - ImmutableList.of("'CZECH_2'", "'2022-02-01 13'")))); - - assertQuery("SELECT * FROM " + fullyQualifiedTestTableName, - "VALUES ('POLAND_1', '2022-02-01 12'), " + - "('POLAND_2', '2022-02-01 12'), " + - "('CZECH_1', '2022-02-01 13'), " + - "('CZECH_2', '2022-02-01 13')"); - assertQuery("SELECT * FROM " + fullyQualifiedTestTableName + " WHERE date_time = '2022-02-01 12'", - "VALUES ('POLAND_1', '2022-02-01 12'), ('POLAND_2', '2022-02-01 12')"); - } - - @Test - public void testAnalyzePartitionedTableWithCanonicalization() - { - String tableName = "test_analyze_table_canonicalization_" + randomNameSuffix(); - assertUpdate("CREATE TABLE %s (a_varchar varchar, month varchar) WITH (partitioned_by = ARRAY['month'])".formatted(getFullyQualifiedTestTableName(tableName))); - - assertUpdate("INSERT INTO " + getFullyQualifiedTestTableName(tableName) + " VALUES ('A', '01'), ('B', '01'), ('C', '02'), ('D', '03')", 4); - - String tableLocation = (String) computeActual("SELECT DISTINCT regexp_replace(\"$path\", '/[^/]*/[^/]*$', '') FROM " + getFullyQualifiedTestTableName(tableName)).getOnlyValue(); - - String externalTableName = "external_" + tableName; - List partitionColumnNames = List.of("month"); - assertUpdate( - """ - CREATE TABLE %s( - a_varchar varchar, - month integer) - WITH ( - partitioned_by = ARRAY['month'], - external_location='%s') - """.formatted(getFullyQualifiedTestTableName(externalTableName), tableLocation)); - - addPartitions(tableName, externalTableName, partitionColumnNames, TupleDomain.all()); - assertQuery("SELECT * FROM " + HIVE_TEST_SCHEMA + ".\"" + externalTableName + "$partitions\"", "VALUES 1, 2, 3"); - assertUpdate("ANALYZE " + getFullyQualifiedTestTableName(externalTableName), 4); - assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(externalTableName), - """ - VALUES - ('a_varchar', 4.0, 2.0, 0.0, null, null, null), - ('month', null, 3.0, 0.0, null, 1, 3), - (null, null, null, null, 4.0, null, null) - """); - - assertUpdate("INSERT INTO " + getFullyQualifiedTestTableName(tableName) + " VALUES ('E', '04')", 1); - addPartitions( - tableName, - externalTableName, - partitionColumnNames, - TupleDomain.fromFixedValues(Map.of("month", new NullableValue(VARCHAR, utf8Slice("04"))))); - assertUpdate("CALL system.flush_metadata_cache(schema_name => '" + HIVE_TEST_SCHEMA + "', table_name => '" + externalTableName + "')"); - assertQuery("SELECT * FROM " + HIVE_TEST_SCHEMA + ".\"" + externalTableName + "$partitions\"", "VALUES 1, 2, 3, 4"); - assertUpdate("ANALYZE " + getFullyQualifiedTestTableName(externalTableName) + " WITH (partitions = ARRAY[ARRAY['04']])", 1); - assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(externalTableName), - """ - VALUES - ('a_varchar', 5.0, 2.0, 0.0, null, null, null), - ('month', null, 4.0, 0.0, null, 1, 4), - (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.*not found.*"); - - assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(externalTableName)); - assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(tableName)); - } - - @Test - public void testExternalLocationWithTrailingSpace() - { - String tableName = "test_external_location_with_trailing_space_" + randomNameSuffix(); - String tableLocationDirWithTrailingSpace = tableName + " "; - String tableLocation = format("s3a://%s/%s/%s", bucketName, HIVE_TEST_SCHEMA, tableLocationDirWithTrailingSpace); - - byte[] contents = "hello\u0001world\nbye\u0001world".getBytes(UTF_8); - String targetPath = format("%s/%s/test.txt", HIVE_TEST_SCHEMA, tableLocationDirWithTrailingSpace); - hiveMinioDataLake.getMinioClient().putObject(bucketName, contents, targetPath); - - assertUpdate(format( - "CREATE TABLE %s (" + - " a varchar, " + - " b varchar) " + - "WITH (format='TEXTFILE', external_location='%s')", - tableName, - tableLocation)); - - assertQuery("SELECT a, b FROM " + tableName, "VALUES ('hello', 'world'), ('bye', 'world')"); - - String actualTableLocation = getTableLocation(tableName); - assertThat(actualTableLocation).isEqualTo(tableLocation); - - assertUpdate("DROP TABLE " + tableName); - } - - @Test - public void testCreateSchemaInvalidName() - { - assertThatThrownBy(() -> assertUpdate("CREATE SCHEMA \".\"")) - .hasMessage("Invalid object name: '.'"); - - assertThatThrownBy(() -> assertUpdate("CREATE SCHEMA \"..\"")) - .hasMessage("Invalid object name: '..'"); - - assertThatThrownBy(() -> assertUpdate("CREATE SCHEMA \"foo/bar\"")) - .hasMessage("Invalid object name: 'foo/bar'"); - } - - @Test - public void testCreateTableInvalidName() - { - assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\".\" (col integer)")) - .hasMessageContaining("Invalid table name"); - assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\"..\" (col integer)")) - .hasMessageContaining("Invalid table name"); - assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\"...\" (col integer)")) - .hasMessage("Invalid table name"); - - for (String tableName : Arrays.asList("foo/bar", "foo/./bar", "foo/../bar")) { - assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\"" + tableName + "\" (col integer)")) - .hasMessage(format("Invalid object name: '%s'", tableName)); - assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + HIVE_TEST_SCHEMA + ".\"" + tableName + "\" (col) AS VALUES 1")) - .hasMessage(format("Invalid object name: '%s'", tableName)); - } - } - - @Test - public void testRenameSchemaToInvalidObjectName() - { - String schemaName = "test_rename_schema_invalid_name_" + randomNameSuffix(); - 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 + "\"")) - .hasMessage(format("Invalid object name: '%s'", invalidSchemaName)); - } - - assertUpdate("DROP SCHEMA " + schemaName); - } - - @Test - public void testRenameTableToInvalidObjectName() - { - String tableName = "test_rename_table_invalid_name_" + randomNameSuffix(); - assertUpdate("CREATE TABLE %s (a_varchar varchar)".formatted(getFullyQualifiedTestTableName(tableName))); - - for (String invalidTableName : Arrays.asList(".", "..", "foo/bar")) { - assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + getFullyQualifiedTestTableName(tableName) + " RENAME TO \"" + invalidTableName + "\"")) - .hasMessage(format("Invalid object name: '%s'", invalidTableName)); - } - - for (String invalidSchemaName : Arrays.asList(".", "..", "foo/bar")) { - assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + getFullyQualifiedTestTableName(tableName) + " RENAME TO \"" + invalidSchemaName + "\".validTableName")) - .hasMessage(format("Invalid object name: '%s'", invalidSchemaName)); - } - - assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(tableName)); - } - - @Test - public void testUnpartitionedTableExternalLocationWithTrainingSlash() - { - String tableName = "test_external_location_trailing_slash_" + randomNameSuffix(); - String tableLocationWithTrailingSlash = format("s3://%s/%s/%s/", bucketName, HIVE_TEST_SCHEMA, tableName); - byte[] contents = "Trino\nSQL\non\neverything".getBytes(UTF_8); - String dataFilePath = format("%s/%s/data.txt", HIVE_TEST_SCHEMA, tableName); - hiveMinioDataLake.getMinioClient().putObject(bucketName, contents, dataFilePath); - - assertUpdate(format( - "CREATE TABLE %s (" + - " a_varchar varchar) " + - "WITH (" + - " external_location='%s'," + - " format='TEXTFILE')", - tableName, - tableLocationWithTrailingSlash)); - assertQuery("SELECT * FROM " + tableName, "VALUES 'Trino', 'SQL', 'on', 'everything'"); - - assertUpdate("DROP TABLE " + tableName); - } - - @Test - public void testUnpartitionedTableExternalLocationOnTopOfTheBucket() - { - String topBucketName = "test-hive-unpartitioned-top-of-the-bucket-" + randomNameSuffix(); - hiveMinioDataLake.getMinio().createBucket(topBucketName); - String tableName = "test_external_location_top_of_the_bucket_" + randomNameSuffix(); - - byte[] contents = "Trino\nSQL\non\neverything".getBytes(UTF_8); - hiveMinioDataLake.getMinioClient().putObject(topBucketName, contents, "data.txt"); - - assertUpdate(format( - "CREATE TABLE %s (" + - " a_varchar varchar) " + - "WITH (" + - " external_location='%s'," + - " format='TEXTFILE')", - tableName, - format("s3://%s/", topBucketName))); - assertQuery("SELECT * FROM " + tableName, "VALUES 'Trino', 'SQL', 'on', 'everything'"); - - assertUpdate("DROP TABLE " + tableName); - } - - @Test - public void testPartitionedTableExternalLocationOnTopOfTheBucket() - { - String topBucketName = "test-hive-partitioned-top-of-the-bucket-" + randomNameSuffix(); - hiveMinioDataLake.getMinio().createBucket(topBucketName); - String tableName = "test_external_location_top_of_the_bucket_" + randomNameSuffix(); - - assertUpdate(format( - "CREATE TABLE %s (" + - " a_varchar varchar, " + - " pkey integer) " + - "WITH (" + - " external_location='%s'," + - " partitioned_by=ARRAY['pkey'])", - tableName, - format("s3://%s/", topBucketName))); - assertUpdate("INSERT INTO " + tableName + " VALUES ('a', 1) , ('b', 1), ('c', 2), ('d', 2)", 4); - assertQuery("SELECT * FROM " + tableName, "VALUES ('a', 1), ('b',1), ('c', 2), ('d', 2)"); - assertUpdate("DELETE FROM " + tableName + " where pkey = 2"); - assertQuery("SELECT * FROM " + tableName, "VALUES ('a', 1), ('b',1)"); - - assertUpdate("DROP TABLE " + tableName); - } - - @Test - public void testDropStatsPartitionedTable() - { - String tableName = "test_hive_drop_stats_partitioned_table_" + randomNameSuffix(); - assertUpdate(("CREATE TABLE %s (" + - " data integer," + - " p_varchar varchar," + - " p_integer integer" + - ") " + - "WITH (" + - " partitioned_by=ARRAY['p_varchar', 'p_integer']" + - ")").formatted(getFullyQualifiedTestTableName(tableName))); - - // Drop stats for partition which does not exist - assertThat(query(format("CALL system.drop_stats('%s', '%s', ARRAY[ARRAY['partnotfound', '999']])", HIVE_TEST_SCHEMA, tableName))) - .failure().hasMessage("No partition found for name: p_varchar=partnotfound/p_integer=999"); - - assertUpdate("INSERT INTO " + getFullyQualifiedTestTableName(tableName) + " VALUES (1, 'part1', 10) , (2, 'part2', 10), (12, 'part2', 20)", 3); - - // Run analyze on the entire table - assertUpdate("ANALYZE " + getFullyQualifiedTestTableName(tableName), 3); - - assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(tableName), - """ - VALUES - ('data', null, 1.0, 0.0, null, 1, 12), - ('p_varchar', 15.0, 2.0, 0.0, null, null, null), - ('p_integer', null, 2.0, 0.0, null, 10, 20), - (null, null, null, null, 3.0, null, null) - """); - - assertUpdate(format("CALL system.drop_stats('%s', '%s', ARRAY[ARRAY['part1', '10']])", HIVE_TEST_SCHEMA, tableName)); - - assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(tableName), - """ - VALUES - ('data', null, 1.0, 0.0, null, 2, 12), - ('p_varchar', 15.0, 2.0, 0.0, null, null, null), - ('p_integer', null, 2.0, 0.0, null, 10, 20), - (null, null, null, null, 3.0, null, null) - """); - - assertUpdate("DELETE FROM " + getFullyQualifiedTestTableName(tableName) + " WHERE p_varchar ='part1' and p_integer = 10"); - - // Drop stats for partition which does not exist - assertThat(query(format("CALL system.drop_stats('%s', '%s', ARRAY[ARRAY['part1', '10']])", HIVE_TEST_SCHEMA, tableName))) - .failure().hasMessage("No partition found for name: p_varchar=part1/p_integer=10"); - - assertQuery("SHOW STATS FOR " + getFullyQualifiedTestTableName(tableName), - """ - VALUES - ('data', null, 1.0, 0.0, null, 2, 12), - ('p_varchar', 10.0, 1.0, 0.0, null, null, null), - ('p_integer', null, 2.0, 0.0, null, 10, 20), - (null, null, null, null, 2.0, null, null) - """); - assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(tableName)); - } - - @Test - public void testUnsupportedDropSchemaCascadeWithNonHiveTable() - { - String schemaName = "test_unsupported_drop_schema_cascade_" + randomNameSuffix(); - String icebergTableName = "test_dummy_iceberg_table" + randomNameSuffix(); - - hiveMinioDataLake.getHiveHadoop().runOnHive("CREATE DATABASE %2$s LOCATION 's3a://%1$s/%2$s'".formatted(bucketName, schemaName)); - try { - hiveMinioDataLake.getHiveHadoop().runOnHive("CREATE TABLE " + schemaName + "." + icebergTableName + " TBLPROPERTIES ('table_type'='iceberg') AS SELECT 1 a"); - - assertQueryFails("DROP SCHEMA " + schemaName + " CASCADE", "\\QCannot query Iceberg table '%s.%s'".formatted(schemaName, icebergTableName)); - - assertThat(computeActual("SHOW SCHEMAS").getOnlyColumnAsSet()).contains(schemaName); - assertThat(computeActual("SHOW TABLES FROM " + schemaName).getOnlyColumnAsSet()).contains(icebergTableName); - assertThat(hiveMinioDataLake.getMinioClient().listObjects(bucketName, schemaName).stream()).isNotEmpty(); - } - finally { - hiveMinioDataLake.getHiveHadoop().runOnHive("DROP DATABASE IF EXISTS " + schemaName + " CASCADE"); - } - } - - @Test - public void testUnsupportedCommentOnHiveView() - { - String viewName = HIVE_TEST_SCHEMA + ".test_unsupported_comment_on_hive_view_" + randomNameSuffix(); - - hiveMinioDataLake.getHiveHadoop().runOnHive("CREATE VIEW " + viewName + " AS SELECT 1 x"); - try { - assertQueryFails("COMMENT ON COLUMN " + viewName + ".x IS NULL", "Hive views are not supported.*"); - } - finally { - hiveMinioDataLake.getHiveHadoop().runOnHive("DROP VIEW " + viewName); - } - } - - @Test - public void testCreateFunction() - { - String name = "test_" + randomNameSuffix(); - String name2 = "test_" + randomNameSuffix(); - - assertUpdate("CREATE FUNCTION " + name + "(x integer) RETURNS bigint RETURN x * 10"); - assertQuery("SELECT " + name + "(99)", "SELECT 990"); - - assertUpdate("CREATE OR REPLACE FUNCTION " + name + "(x integer) RETURNS bigint COMMENT 't42' RETURN x * 42"); - assertQuery("SELECT " + name + "(99)", "SELECT 4158"); - - assertQueryFails("SELECT " + name + "(2.9)", ".*Unexpected parameters.*"); - - assertUpdate("CREATE FUNCTION " + name + "(x double) RETURNS double COMMENT 't88' RETURN x * 8.8"); - - assertThat(query("SHOW FUNCTIONS")) - .result() - .skippingTypesCheck() - .containsAll(resultBuilder(getSession()) - .row(name, "bigint", "integer", "scalar", true, "t42") - .row(name, "double", "double", "scalar", true, "t88") - .build()); - - assertQuery("SELECT " + name + "(99)", "SELECT 4158"); - assertQuery("SELECT " + name + "(2.9)", "SELECT 25.52"); - - assertQueryFails("CREATE FUNCTION " + name + "(x int) RETURNS bigint RETURN x", "line 1:1: Function already exists"); - - assertQuery("SELECT " + name + "(99)", "SELECT 4158"); - assertQuery("SELECT " + name + "(2.9)", "SELECT 25.52"); - - assertUpdate("CREATE OR REPLACE FUNCTION " + name + "(x bigint) RETURNS bigint RETURN x * 23"); - assertUpdate("CREATE FUNCTION " + name2 + "(s varchar) RETURNS varchar RETURN 'Hello ' || s"); - - assertThat(query("SHOW FUNCTIONS")) - .result() - .skippingTypesCheck() - .containsAll(resultBuilder(getSession()) - .row(name, "bigint", "integer", "scalar", true, "t42") - .row(name, "bigint", "bigint", "scalar", true, "") - .row(name, "double", "double", "scalar", true, "t88") - .row(name2, "varchar", "varchar", "scalar", true, "") - .build()); - - assertQuery("SELECT " + name + "(99)", "SELECT 4158"); - assertQuery("SELECT " + name + "(cast(99 as bigint))", "SELECT 2277"); - assertQuery("SELECT " + name + "(2.9)", "SELECT 25.52"); - assertQuery("SELECT " + name2 + "('world')", "SELECT 'Hello world'"); - - assertQueryFails("DROP FUNCTION " + name + "(varchar)", "line 1:1: Function not found"); - assertUpdate("DROP FUNCTION " + name + "(z bigint)"); - assertUpdate("DROP FUNCTION " + name + "(double)"); - assertUpdate("DROP FUNCTION " + name + "(int)"); - assertQueryFails("DROP FUNCTION " + name + "(bigint)", "line 1:1: Function not found"); - assertUpdate("DROP FUNCTION IF EXISTS " + name + "(bigint)"); - assertUpdate("DROP FUNCTION " + name2 + "(varchar)"); - assertQueryFails("DROP FUNCTION " + name2 + "(varchar)", "line 1:1: Function not found"); - } - - private void renamePartitionResourcesOutsideTrino(String tableName, String partitionColumn, String regionKey) - { - String partitionName = format("%s=%s", partitionColumn, regionKey); - String partitionS3KeyPrefix = format("%s/%s/%s", HIVE_TEST_SCHEMA, tableName, partitionName); - String renamedPartitionSuffix = "CP"; - - // Copy whole partition to new location - MinioClient minioClient = hiveMinioDataLake.getMinioClient(); - minioClient.listObjects(bucketName, "") - .forEach(objectKey -> { - if (objectKey.startsWith(partitionS3KeyPrefix)) { - String fileName = objectKey.substring(objectKey.lastIndexOf('/')); - String destinationKey = partitionS3KeyPrefix + renamedPartitionSuffix + fileName; - minioClient.copyObject(bucketName, objectKey, bucketName, destinationKey); - } - }); - - // Delete old partition and update metadata to point to location of new copy - Table hiveTable = metastoreClient.getTable(HIVE_TEST_SCHEMA, tableName).orElseThrow(); - Partition partition = metastoreClient.getPartition(hiveTable, List.of(regionKey)).orElseThrow(); - Map> partitionStatistics = metastoreClient.getPartitionColumnStatistics( - HIVE_TEST_SCHEMA, - tableName, - ImmutableSet.of(partitionName), - partition.getColumns().stream().map(Column::getName).collect(toSet())); - - metastoreClient.dropPartition(HIVE_TEST_SCHEMA, tableName, List.of(regionKey), true); - metastoreClient.addPartitions(HIVE_TEST_SCHEMA, tableName, List.of( - new PartitionWithStatistics( - Partition.builder(partition) - .withStorage(builder -> builder.setLocation( - partition.getStorage().getLocation() + renamedPartitionSuffix)) - .build(), - partitionName, - new PartitionStatistics(getHiveBasicStatistics(partition.getParameters()), partitionStatistics.get(partitionName))))); - } - - protected void assertInsertFailure(String testTable, String expectedMessageRegExp) - { - assertInsertFailure(getSession(), testTable, expectedMessageRegExp); - } - - protected void assertInsertFailure(Session session, String testTable, String expectedMessageRegExp) - { - assertQueryFails( - session, - createInsertAsSelectFromTpchStatement(testTable), - expectedMessageRegExp); - } - - private String createInsertAsSelectFromTpchStatement(String testTable) - { - return format("INSERT INTO %s " + - "SELECT name, comment, nationkey, regionkey " + - "FROM tpch.tiny.nation", - testTable); - } - - protected String createInsertStatement(String testTable, List> data) - { - String values = data.stream() - .map(row -> String.join(", ", row)) - .collect(Collectors.joining("), (")); - return format("INSERT INTO %s VALUES (%s)", testTable, values); - } - - protected void assertOverwritePartition(String testTable) - { - computeActual(createInsertStatement( - testTable, - ImmutableList.of( - ImmutableList.of("'POLAND'", "'Test Data'", "25", "5"), - ImmutableList.of("'CZECH'", "'Test Data'", "26", "5")))); - query(format("SELECT name, comment, nationkey, regionkey FROM %s WHERE regionkey = 5", testTable)) - .assertThat() - .result() - .skippingTypesCheck() - .containsAll(resultBuilder(getSession()) - .row("POLAND", "Test Data", 25L, 5L) - .row("CZECH", "Test Data", 26L, 5L) - .build()); - - computeActual(createInsertStatement( - testTable, - ImmutableList.of( - ImmutableList.of("'POLAND'", "'Overwrite'", "25", "5")))); - query(format("SELECT name, comment, nationkey, regionkey FROM %s WHERE regionkey = 5", testTable)) - .assertThat() - .result() - .skippingTypesCheck() - .containsAll(resultBuilder(getSession()) - .row("POLAND", "Overwrite", 25L, 5L) - .build()); - computeActual(format("DROP TABLE %s", testTable)); - } - - protected String getRandomTestTableName() - { - return "nation_" + randomNameSuffix(); - } - - protected String getFullyQualifiedTestTableName() - { - return getFullyQualifiedTestTableName(getRandomTestTableName()); - } - - protected String getFullyQualifiedTestTableName(String tableName) - { - return getFullyQualifiedTestTableName(HIVE_TEST_SCHEMA, tableName); - } - - protected String getFullyQualifiedTestTableName(String schemaName, String tableName) - { - return "hive.%s.%s".formatted(schemaName, tableName); - } - - protected String getHiveTestTableName(String tableName) - { - return getHiveTestTableName(HIVE_TEST_SCHEMA, tableName); - } - - protected String getHiveTestTableName(String schemaName, String tableName) - { - return "%s.%s".formatted(schemaName, tableName); - } - - protected String getCreateTableStatement(String tableName, String... propertiesEntries) - { - return getCreateTableStatement(tableName, Arrays.asList(propertiesEntries)); - } - - protected String getCreateTableStatement(String tableName, List propertiesEntries) - { - return format( - "CREATE TABLE %s (" + - " name varchar(25), " + - " comment varchar(152), " + - " nationkey bigint, " + - " regionkey bigint) " + - (propertiesEntries.isEmpty() ? "" : propertiesEntries - .stream() - .collect(joining(",", "WITH (", ")"))), - tableName); - } - - protected void copyTpchNationToTable(String testTable) - { - computeActual(format("INSERT INTO " + testTable + " SELECT name, comment, nationkey, regionkey FROM tpch.tiny.nation")); - } - - private void testWriteWithFileSize(String testTable, int scaleFactorInThousands, long fileSizeRangeStart, long fileSizeRangeEnd) - { - String scaledColumnExpression = format("array_join(transform(sequence(1, %d), x-> array_join(repeat(comment, 1000), '')), '')", scaleFactorInThousands); - computeActual(format("INSERT INTO " + testTable + " SELECT %s, %s, regionkey FROM tpch.tiny.nation WHERE nationkey = 9", scaledColumnExpression, scaledColumnExpression)); - query(format("SELECT length(col1) FROM %s", testTable)) - .assertThat() - .result() - .skippingTypesCheck() - .containsAll(resultBuilder(getSession()) - .row(114L * scaleFactorInThousands * 1000) - .build()); - query(format("SELECT \"$file_size\" BETWEEN %d AND %d FROM %s", fileSizeRangeStart, fileSizeRangeEnd, testTable)) - .assertThat() - .result() - .skippingTypesCheck() - .containsAll(resultBuilder(getSession()) - .row(true) - .build()); - } - - private void addPartitions( - String sourceTableName, - String destinationExternalTableName, - List columnNames, - TupleDomain partitionsKeyFilter) - { - Optional> partitionNames = metastoreClient.getPartitionNamesByFilter(HIVE_TEST_SCHEMA, sourceTableName, columnNames, partitionsKeyFilter); - if (partitionNames.isEmpty()) { - // nothing to add - return; - } - Table table = metastoreClient.getTable(HIVE_TEST_SCHEMA, sourceTableName) - .orElseThrow(() -> new TableNotFoundException(new SchemaTableName(HIVE_TEST_SCHEMA, sourceTableName))); - Map> partitionsByNames = metastoreClient.getPartitionsByNames(table, partitionNames.get()); - - metastoreClient.addPartitions( - HIVE_TEST_SCHEMA, - destinationExternalTableName, - partitionsByNames.entrySet().stream() - .map(e -> new PartitionWithStatistics( - e.getValue() - .map(p -> Partition.builder(p).setTableName(destinationExternalTableName).build()) - .orElseThrow(), - e.getKey(), - PartitionStatistics.empty())) - .collect(toImmutableList())); - } - - private String getTableLocation(String tableName) - { - return (String) computeScalar("SELECT DISTINCT regexp_replace(\"$path\", '/[^/]*$', '') FROM " + tableName); - } + private static final String BUCKET_NAME = "test-hive-insert-overwrite-" + randomNameSuffix(); - @Test - public void testInsertOverwritePartitionedAndBucketedAcidTable() + public TestHive3OnDataLake() { - String testTable = getFullyQualifiedTestTableName(); - computeActual(getCreateTableStatement( - testTable, - "partitioned_by=ARRAY['regionkey']", - "bucketed_by = ARRAY['nationkey']", - "bucket_count = 3", - "format = 'ORC'", - "transactional = true")); - assertInsertFailure( - testTable, - "Overwriting existing partition in transactional tables doesn't support DIRECT_TO_TARGET_EXISTING_DIRECTORY write mode"); + super(BUCKET_NAME, new Hive3MinioDataLake(BUCKET_NAME, HiveHadoop.HIVE3_IMAGE)); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive4OnDataLake.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive4OnDataLake.java new file mode 100644 index 000000000000..379de81fcc91 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive4OnDataLake.java @@ -0,0 +1,66 @@ +/* + * 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.plugin.hive.containers.Hive4MinioDataLake; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.junit.jupiter.api.Assumptions.abort; + +@Execution(ExecutionMode.SAME_THREAD) // TODO Make custom hive4 image to support running queries concurrently +class TestHive4OnDataLake + extends BaseTestHiveOnDataLake +{ + private static final String BUCKET_NAME = "test-hive-insert-overwrite-" + randomNameSuffix(); + + public TestHive4OnDataLake() + { + super(BUCKET_NAME, new Hive4MinioDataLake(BUCKET_NAME)); + } + + @Override + @Test + public void testSyncPartitionOnBucketRoot() + { + // https://github.com/trinodb/trino/issues/24453 + abort("Fails with `location must not be root path`"); + } + + @Override + @Test + public void testUnpartitionedTableExternalLocationOnTopOfTheBucket() + { + // https://github.com/trinodb/trino/issues/24453 + abort("Fails with `location must not be root path`"); + } + + @Override + @Test + public void testPartitionedTableExternalLocationOnTopOfTheBucket() + { + // https://github.com/trinodb/trino/issues/24453 + abort("Fails with `location must not be root path`"); + } + + @Override + @Test + public void testInsertOverwritePartitionedAndBucketedAcidTable() + { + // https://github.com/trinodb/trino/issues/24454 + abort("Fails with `Processor has no capabilities, cannot create an ACID table`"); + } +} 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 f986576683df..0f5fc357ea67 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 @@ -14,7 +14,7 @@ package io.trino.plugin.hive; import io.airlift.units.Duration; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.s3.S3HiveQueryRunner; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; @@ -28,13 +28,13 @@ public class TestHiveAnalyzeCorruptStatistics extends AbstractTestQueryFramework { - private HiveMinioDataLake hiveMinioDataLake; + private Hive3MinioDataLake hiveMinioDataLake; @Override protected QueryRunner createQueryRunner() throws Exception { - hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake("test-analyze")); + hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake("test-analyze")); hiveMinioDataLake.start(); return S3HiveQueryRunner.builder(hiveMinioDataLake) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConfig.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConfig.java index cfeedd94941c..d07f3a58b821 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConfig.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConfig.java @@ -29,7 +29,6 @@ import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; import static io.airlift.units.DataSize.Unit.GIGABYTE; import static io.airlift.units.DataSize.Unit.MEGABYTE; -import static io.trino.plugin.hive.HiveConfig.CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED; import static io.trino.plugin.hive.HiveSessionProperties.InsertExistingPartitionsBehavior.APPEND; import static io.trino.plugin.hive.HiveSessionProperties.InsertExistingPartitionsBehavior.OVERWRITE; import static io.trino.plugin.hive.util.TestHiveUtil.nonDefaultTimeZone; @@ -203,7 +202,7 @@ public void testExplicitPropertyMappings() .put("hive.delta-lake-catalog-name", "delta") .put("hive.hudi-catalog-name", "hudi") .put("hive.auto-purge", "true") - .put(CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED, "true") + .put("hive.partition-projection-enabled", "true") .put("hive.s3.storage-class-filter", "READ_NON_GLACIER_AND_RESTORED") .put("hive.metadata.parallelism", "10") .buildOrThrow(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCustomCatalogConnectorSmokeTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCustomCatalogConnectorSmokeTest.java index 35433d3610da..ced91cf9a85d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCustomCatalogConnectorSmokeTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCustomCatalogConnectorSmokeTest.java @@ -15,9 +15,9 @@ import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.containers.HiveHadoop; -import io.trino.plugin.hive.containers.HiveMinioDataLake; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.security.PrincipalType; import io.trino.testing.BaseConnectorSmokeTest; import io.trino.testing.QueryRunner; @@ -49,7 +49,7 @@ protected QueryRunner createQueryRunner() throws Exception { String bucketName = "test-hive-metastore-catalog-smoke-test-" + randomNameSuffix(); - HiveMinioDataLake hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName, HiveHadoop.HIVE3_IMAGE)); + Hive3MinioDataLake hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName, HiveHadoop.HIVE3_IMAGE)); hiveMinioDataLake.start(); // Inserting into metastore's database directly because the Hive does not expose a way to create a custom catalog @@ -57,7 +57,7 @@ protected QueryRunner createQueryRunner() QueryRunner queryRunner = HiveQueryRunner.builder() .addHiveProperty("hive.metastore", "thrift") - .addHiveProperty("hive.metastore.uri", hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint().toString()) + .addHiveProperty("hive.metastore.uri", hiveMinioDataLake.getHiveMetastoreEndpoint().toString()) .addHiveProperty("hive.metastore.thrift.catalog-name", HIVE_CUSTOM_CATALOG) .addHiveProperty("fs.hadoop.enabled", "false") .addHiveProperty("fs.native-s3.enabled", "true") 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 332f2a3ae9bb..d4efe5fe6fc3 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 @@ -133,12 +133,12 @@ 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.metastore.Partitions.HIVE_DEFAULT_DYNAMIC_PARTITION; 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; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveMetadataListing.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveMetadataListing.java index bb96fefa3cad..af734b91ec22 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveMetadataListing.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveMetadataListing.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import io.trino.metastore.Column; import io.trino.metastore.Database; import io.trino.metastore.HiveBucketProperty; @@ -244,6 +245,12 @@ public List getTables(String databaseName) .build(); } + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, ImmutableSet parameterValues) + { + throw new UnsupportedOperationException(); + } + @Override public Optional
getTable(String databaseName, String tableName) { 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 b0bb0e9854ff..af40a28e0d83 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 @@ -25,9 +25,9 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.operator.FlatHashStrategyCompiler; import io.trino.operator.GroupByHashPageIndexerFactory; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.HivePageSinkMetadata; import io.trino.spi.Page; import io.trino.spi.PageBuilder; 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 54b05e04f833..fd36b81605ec 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 @@ -17,7 +17,7 @@ import io.trino.operator.RetryPolicy; import io.trino.plugin.exchange.filesystem.FileSystemExchangePlugin; import io.trino.plugin.exchange.filesystem.containers.MinioStorage; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.s3.S3HiveQueryRunner; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; @@ -43,7 +43,7 @@ public TestHiveQueryFailureRecoveryTest() super(RetryPolicy.QUERY); } - private HiveMinioDataLake hiveMinioDataLake; + private Hive3MinioDataLake hiveMinioDataLake; private MinioStorage minioStorage; @Override @@ -55,7 +55,7 @@ protected QueryRunner createQueryRunner( throws Exception { String bucketName = "test-hive-insert-overwrite-" + randomNameSuffix(); // randomizing bucket name to ensure cached TrinoS3FileSystem objects are not reused - this.hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + this.hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); this.minioStorage = closeAfterClass(new MinioStorage("test-exchange-spooling-" + randomNameSuffix())); 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 b602f01cb00d..cf5076c377d9 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 @@ -33,10 +33,10 @@ import static io.trino.plugin.hive.TestingHiveUtils.getConnectorService; import static io.trino.spi.security.SelectedRole.Type.ROLE; import static io.trino.testing.MaterializedResult.resultBuilder; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; 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.assertThatThrownBy; @@ -45,7 +45,7 @@ public class TestHiveS3AndGlueMetastoreTest { public TestHiveS3AndGlueMetastoreTest() { - super("partitioned_by", "external_location", requireNonNull(System.getenv("S3_BUCKET"), "Environment S3_BUCKET was not set")); + super("partitioned_by", "external_location", requireEnv("S3_BUCKET")); } @Override 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 a74f29d305d8..cca5e7a009c0 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 @@ -17,7 +17,7 @@ import io.trino.operator.RetryPolicy; import io.trino.plugin.exchange.filesystem.FileSystemExchangePlugin; import io.trino.plugin.exchange.filesystem.containers.MinioStorage; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.s3.S3HiveQueryRunner; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; @@ -43,7 +43,7 @@ public TestHiveTaskFailureRecoveryTest() super(RetryPolicy.TASK); } - private HiveMinioDataLake hiveMinioDataLake; + private Hive3MinioDataLake hiveMinioDataLake; private MinioStorage minioStorage; @Override @@ -55,7 +55,7 @@ protected QueryRunner createQueryRunner( throws Exception { String bucketName = "test-hive-insert-overwrite-" + randomNameSuffix(); // randomizing bucket name to ensure cached TrinoS3FileSystem objects are not reused - this.hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + this.hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); this.minioStorage = closeAfterClass(new MinioStorage("test-exchange-spooling-" + randomNameSuffix())); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive3MinioDataLake.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive3MinioDataLake.java new file mode 100644 index 000000000000..840e18d55bf9 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive3MinioDataLake.java @@ -0,0 +1,74 @@ +/* + * 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.containers; + +import com.google.common.collect.ImmutableMap; + +import java.net.URI; +import java.util.Map; + +import static io.trino.plugin.hive.containers.HiveMinioDataLake.State.STARTED; +import static io.trino.testing.containers.TestContainers.getPathFromClassPathResource; + +public class Hive3MinioDataLake + extends HiveMinioDataLake +{ + private final HiveHadoop hiveHadoop; + + public Hive3MinioDataLake(String bucketName) + { + this(bucketName, HiveHadoop.HIVE3_IMAGE); + } + + public Hive3MinioDataLake(String bucketName, String hiveHadoopImage) + { + this(bucketName, ImmutableMap.of("/etc/hadoop/conf/core-site.xml", getPathFromClassPathResource("hive_minio_datalake/hive-core-site.xml")), hiveHadoopImage); + } + + public Hive3MinioDataLake(String bucketName, Map hiveHadoopFilesToMount, String hiveHadoopImage) + { + super(bucketName); + HiveHadoop.Builder hiveHadoopBuilder = HiveHadoop.builder() + .withImage(hiveHadoopImage) + .withNetwork(network) + .withFilesToMount(hiveHadoopFilesToMount); + this.hiveHadoop = closer.register(hiveHadoopBuilder.build()); + } + + @Override + public void start() + { + super.start(); + hiveHadoop.start(); + state = STARTED; + } + + @Override + public String runOnHive(String sql) + { + return hiveHadoop.runOnHive(sql); + } + + @Override + public HiveHadoop getHiveHadoop() + { + return hiveHadoop; + } + + @Override + public URI getHiveMetastoreEndpoint() + { + return hiveHadoop.getHiveMetastoreEndpoint(); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive4HiveServer.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive4HiveServer.java new file mode 100644 index 000000000000..96af084ca48c --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive4HiveServer.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.hive.containers; + +import com.google.common.collect.ImmutableSet; +import com.google.common.net.HostAndPort; +import io.airlift.log.Logger; +import io.trino.testing.containers.BaseTestContainer; +import org.testcontainers.containers.Network; + +import java.net.URI; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static io.trino.plugin.hive.containers.Hive4Metastore.HIVE4_IMAGE; + +public class Hive4HiveServer + extends BaseTestContainer +{ + public static final int HIVE_SERVER_PORT = 10000; + + private static final Logger log = Logger.get(Hive4HiveServer.class); + private static final String HOST_NAME = "hiveserver2"; + + public static Builder builder() + { + return new Builder(); + } + + public static class Builder + extends BaseTestContainer.Builder + { + private Builder() + { + this.image = HIVE4_IMAGE; + this.hostName = HOST_NAME; + this.exposePorts = ImmutableSet.of(HIVE_SERVER_PORT); + } + + @Override + public Hive4HiveServer build() + { + return new Hive4HiveServer(image, hostName, exposePorts, filesToMount, envVars, network, startupRetryLimit); + } + } + + private Hive4HiveServer( + String image, + String hostName, + Set ports, + Map filesToMount, + Map envVars, + Optional network, + int startupRetryLimit) + { + super( + image, + hostName, + ports, + filesToMount, + envVars, + network, + startupRetryLimit); + } + + @Override + public void start() + { + super.start(); + log.info("Hive container started with addresses for hive server: %s", getHiveServerEndpoint()); + } + + public String runOnHive(String query) + { + return executeInContainerFailOnError("beeline", "-u", "jdbc:hive2://localhost:%s/default".formatted(HIVE_SERVER_PORT), "-n", "hive", "-e", query); + } + + public URI getHiveServerEndpoint() + { + HostAndPort address = getMappedHostAndPortForExposedPort(HIVE_SERVER_PORT); + return URI.create(address.getHost() + ":" + address.getPort()); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive4Metastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive4Metastore.java new file mode 100644 index 000000000000..c7d01ca36345 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive4Metastore.java @@ -0,0 +1,96 @@ +/* + * 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.containers; + +import com.google.common.collect.ImmutableSet; +import com.google.common.net.HostAndPort; +import io.airlift.log.Logger; +import io.trino.testing.containers.BaseTestContainer; +import org.testcontainers.containers.Network; + +import java.net.URI; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static io.trino.testing.TestingProperties.getDockerImagesVersion; + +public class Hive4Metastore + extends BaseTestContainer +{ + public static final String HIVE4_IMAGE = "ghcr.io/trinodb/testing/hive4.0-hive:" + getDockerImagesVersion(); + public static final int HIVE_METASTORE_PORT = 9083; + + private static final Logger log = Logger.get(HiveHadoop.class); + private static final String HOST_NAME = "metastore"; + + public static Builder builder() + { + return new Builder(); + } + + public static class Builder + extends BaseTestContainer.Builder + { + private Builder() + { + this.image = HIVE4_IMAGE; + this.hostName = HOST_NAME; + this.exposePorts = ImmutableSet.of(HIVE_METASTORE_PORT); + } + + @Override + public Hive4Metastore build() + { + return new Hive4Metastore(image, hostName, exposePorts, filesToMount, envVars, network, startupRetryLimit); + } + } + + private Hive4Metastore( + String image, + String hostName, + Set ports, + Map filesToMount, + Map envVars, + Optional network, + int startupRetryLimit) + { + super( + image, + hostName, + ports, + filesToMount, + envVars, + network, + startupRetryLimit); + } + + @Override + public void start() + { + super.start(); + log.info("Hive container started with addresses for metastore: %s", getHiveMetastoreEndpoint()); + } + + public URI getHiveMetastoreEndpoint() + { + HostAndPort address = getMappedHostAndPortForExposedPort(HIVE_METASTORE_PORT); + return URI.create("thrift://" + address.getHost() + ":" + address.getPort()); + } + + public URI getInternalHiveMetastoreEndpoint() + { + return URI.create("thrift://" + HOST_NAME + ":" + HIVE_METASTORE_PORT); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive4MinioDataLake.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive4MinioDataLake.java new file mode 100644 index 000000000000..e58ea118889b --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/Hive4MinioDataLake.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.plugin.hive.containers; + +import com.google.common.collect.ImmutableMap; + +import java.net.URI; +import java.util.Map; +import java.util.Set; + +import static io.trino.plugin.hive.containers.Hive4HiveServer.HIVE_SERVER_PORT; +import static io.trino.plugin.hive.containers.Hive4Metastore.HIVE4_IMAGE; +import static io.trino.plugin.hive.containers.HiveMinioDataLake.State.STARTED; +import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; +import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; +import static io.trino.testing.containers.TestContainers.getPathFromClassPathResource; + +public class Hive4MinioDataLake + extends HiveMinioDataLake +{ + private final Hive4HiveServer hiveServer; + private final Hive4Metastore hiveMetastore; + + public Hive4MinioDataLake(String bucketName) + { + super(bucketName); + String hiveImage = HIVE4_IMAGE; + Map hiveFilesToMount = ImmutableMap.of("/opt/hive/conf/hive-site.xml", getPathFromClassPathResource("hive_minio_datalake/hive4-hive-site.xml")); + // Separate hms and hiveserver(below) is created as standalone hiveserver doesn't expose embedded hms. https://github.com/apache/hive/blob/a1420ed816c315d98be7ebf05cdc3ba139a68643/packaging/src/docker/README.md?plain=1#L46. + // Run standalone metastore https://github.com/apache/hive/blob/a1420ed816c315d98be7ebf05cdc3ba139a68643/packaging/src/docker/README.md?plain=1#L105 + Hive4Metastore.Builder metastorebuilder = Hive4Metastore.builder() + .withImage(hiveImage) + .withEnvVars(Map.of("SERVICE_NAME", "metastore")) + .withNetwork(network) + .withExposePorts(Set.of(Hive4Metastore.HIVE_METASTORE_PORT)) + .withFilesToMount(hiveFilesToMount); + this.hiveMetastore = closer.register(metastorebuilder.build()); + + // Run hive server connecting to remote(above) metastore https://github.com/apache/hive/blob/a1420ed816c315d98be7ebf05cdc3ba139a68643/packaging/src/docker/README.md?plain=1#L139-L143 + Hive4HiveServer.Builder hiveHadoopBuilder = Hive4HiveServer.builder() + .withImage(hiveImage) + .withEnvVars(Map.of( + "SERVICE_NAME", "hiveserver2", + "HIVE_SERVER2_THRIFT_PORT", String.valueOf(HIVE_SERVER_PORT), + "SERVICE_OPTS", "-Xmx1G -Dhive.metastore.uris=%s".formatted(hiveMetastore.getInternalHiveMetastoreEndpoint()), + "IS_RESUME", "true", + "AWS_ACCESS_KEY_ID", MINIO_ACCESS_KEY, + "AWS_SECRET_KEY", MINIO_SECRET_KEY)) + .withNetwork(network) + .withExposePorts(Set.of(HIVE_SERVER_PORT)) + .withFilesToMount(hiveFilesToMount); + this.hiveServer = closer.register(hiveHadoopBuilder.build()); + } + + @Override + public void start() + { + super.start(); + hiveMetastore.start(); + hiveServer.start(); + state = STARTED; + } + + @Override + public String runOnHive(String sql) + { + return hiveServer.runOnHive(sql); + } + + @Override + public HiveHadoop getHiveHadoop() + { + throw new UnsupportedOperationException(); + } + + @Override + public URI getHiveMetastoreEndpoint() + { + return hiveMetastore.getHiveMetastoreEndpoint(); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/HiveMinioDataLake.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/HiveMinioDataLake.java index 0ce79c2ff6a6..19bbc35d32a4 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/HiveMinioDataLake.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/HiveMinioDataLake.java @@ -19,50 +19,36 @@ import io.trino.testing.minio.MinioClient; import org.testcontainers.containers.Network; +import java.net.URI; import java.util.List; -import java.util.Map; import static com.google.common.base.Preconditions.checkState; +import static io.trino.plugin.hive.containers.HiveMinioDataLake.State.INITIAL; +import static io.trino.plugin.hive.containers.HiveMinioDataLake.State.STARTED; +import static io.trino.plugin.hive.containers.HiveMinioDataLake.State.STARTING; +import static io.trino.plugin.hive.containers.HiveMinioDataLake.State.STOPPED; import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; -import static io.trino.testing.containers.TestContainers.getPathFromClassPathResource; import static java.util.Objects.requireNonNull; import static org.testcontainers.containers.Network.newNetwork; -public class HiveMinioDataLake +public abstract class HiveMinioDataLake implements AutoCloseable { - /** - * In S3 this region is implicitly the default one. In Minio, however, - * if we set an empty region, it will accept any. - * So setting it by default to `us-east-1` simulates S3 better - */ - public static final String MINIO_DEFAULT_REGION = "us-east-1"; + private static final String MINIO_DEFAULT_REGION = "us-east-1"; private final String bucketName; private final Minio minio; - private final HiveHadoop hiveHadoop; - - private final AutoCloseableCloser closer = AutoCloseableCloser.create(); - private final Network network; - - private State state = State.INITIAL; private MinioClient minioClient; - public HiveMinioDataLake(String bucketName) - { - this(bucketName, HiveHadoop.HIVE3_IMAGE); - } - - public HiveMinioDataLake(String bucketName, String hiveHadoopImage) - { - this(bucketName, ImmutableMap.of("/etc/hadoop/conf/core-site.xml", getPathFromClassPathResource("hive_minio_datalake/hive-core-site.xml")), hiveHadoopImage); - } + protected final AutoCloseableCloser closer = AutoCloseableCloser.create(); + protected final Network network; + protected State state = INITIAL; - public HiveMinioDataLake(String bucketName, Map hiveHadoopFilesToMount, String hiveHadoopImage) + public HiveMinioDataLake(String bucketName) { this.bucketName = requireNonNull(bucketName, "bucketName is null"); - network = closer.register(newNetwork()); + this.network = closer.register(newNetwork()); this.minio = closer.register( Minio.builder() .withNetwork(network) @@ -72,30 +58,22 @@ public HiveMinioDataLake(String bucketName, Map hiveHadoopFilesT .put("MINIO_REGION", MINIO_DEFAULT_REGION) .buildOrThrow()) .build()); - - HiveHadoop.Builder hiveHadoopBuilder = HiveHadoop.builder() - .withImage(hiveHadoopImage) - .withNetwork(network) - .withFilesToMount(hiveHadoopFilesToMount); - this.hiveHadoop = closer.register(hiveHadoopBuilder.build()); } public void start() { - checkState(state == State.INITIAL, "Already started: %s", state); - state = State.STARTING; + checkState(state == INITIAL, "Already started: %s", state); + state = STARTING; minio.start(); - hiveHadoop.start(); minioClient = closer.register(minio.createMinioClient()); minio.createBucket(bucketName); - state = State.STARTED; } public void stop() throws Exception { closer.close(); - state = State.STOPPED; + state = STOPPED; } public Network getNetwork() @@ -105,7 +83,7 @@ public Network getNetwork() public MinioClient getMinioClient() { - checkState(state == State.STARTED, "Can't provide client when MinIO state is: %s", state); + checkState(state == STARTED, "Can't provide client when MinIO state is: %s", state); return minioClient; } @@ -129,10 +107,11 @@ public Minio getMinio() return minio; } - public HiveHadoop getHiveHadoop() - { - return hiveHadoop; - } + public abstract String runOnHive(String sql); + + public abstract HiveHadoop getHiveHadoop(); + + public abstract URI getHiveMetastoreEndpoint(); public String getBucketName() { @@ -146,7 +125,7 @@ public void close() stop(); } - private enum State + protected enum State { INITIAL, STARTING, 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 b37bdddc2825..53324069d5f0 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 @@ -16,10 +16,10 @@ import com.google.common.collect.ImmutableList; import io.trino.filesystem.Location; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.Table; import io.trino.plugin.hive.HiveQueryRunner; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.MaterializedRow; import io.trino.testing.QueryRunner; 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 index 0f7e63dcbae6..ffb7e8a2451a 100644 --- 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 @@ -16,9 +16,9 @@ import io.trino.metastore.Column; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.SchemaAlreadyExistsException; import io.trino.metastore.Table; -import io.trino.plugin.hive.SchemaAlreadyExistsException; -import io.trino.plugin.hive.TableAlreadyExistsException; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.spi.connector.TableNotFoundException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; 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 148ec287cd1f..c9e62ef88fae 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 @@ -31,6 +31,7 @@ import io.trino.metastore.PartitionStatistics; import io.trino.metastore.Table; import io.trino.metastore.TableInfo; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.base.util.AutoCloseableCloser; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; @@ -72,12 +73,12 @@ import static io.trino.metastore.HiveColumnStatistics.createIntegerColumnStatistics; import static io.trino.metastore.HiveType.HIVE_STRING; import static io.trino.metastore.StatisticsUpdateMode.MERGE_INCREMENTAL; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; import static io.trino.plugin.hive.metastore.MetastoreUtil.computePartitionKeyFilter; import static io.trino.plugin.hive.metastore.MetastoreUtil.makePartitionName; -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; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreWithQueryRunner.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreWithQueryRunner.java index 2bdfd788ff01..ea53c3d45836 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreWithQueryRunner.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreWithQueryRunner.java @@ -19,9 +19,9 @@ import com.google.inject.Key; import io.trino.Session; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.plugin.hive.HiveQueryRunner; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; import io.trino.spi.security.Identity; import io.trino.spi.security.SelectedRole; import io.trino.testing.AbstractTestQueryFramework; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastoreSkipArchive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastoreSkipArchive.java index 19a0c276d336..338091df23ce 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastoreSkipArchive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastoreSkipArchive.java @@ -63,7 +63,7 @@ void cleanUpSchema() @Test void testSkipArchive() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_skip_archive", "(col int)")) { + try (TestTable table = newTrinoTable("test_skip_archive", "(col int)")) { List tableVersionsBeforeInsert = getTableVersions(testSchema, table.getName()); assertThat(tableVersionsBeforeInsert).hasSize(1); String versionIdBeforeInsert = getOnlyElement(tableVersionsBeforeInsert).versionId(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/v1/TestGlueInputConverter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/v1/TestGlueInputConverter.java index e3d713056449..b2c3c42da904 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/v1/TestGlueInputConverter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/v1/TestGlueInputConverter.java @@ -27,8 +27,6 @@ import io.trino.metastore.Partition; import io.trino.metastore.Storage; import io.trino.metastore.Table; -import io.trino.plugin.hive.metastore.glue.v1.converter.GlueInputConverter; -import io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter; import io.trino.spi.function.LanguageFunction; import org.junit.jupiter.api.Test; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/v1/TestGlueToTrinoConverter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/v1/TestGlueToTrinoConverter.java index e7231d6ee928..49356c25abe9 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/v1/TestGlueToTrinoConverter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/v1/TestGlueToTrinoConverter.java @@ -22,8 +22,7 @@ import io.trino.metastore.Column; import io.trino.metastore.HiveBucketProperty; import io.trino.metastore.Storage; -import io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter; -import io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.GluePartitionConverter; +import io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.GluePartitionConverter; import io.trino.spi.security.PrincipalType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -37,16 +36,16 @@ import static com.amazonaws.util.CollectionUtils.isNullOrEmpty; import static io.trino.metastore.HiveType.HIVE_STRING; import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getPartitionParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getStorageDescriptor; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableTypeNullable; import static io.trino.plugin.hive.metastore.glue.v1.TestingMetastoreObjects.getGlueTestColumn; import static io.trino.plugin.hive.metastore.glue.v1.TestingMetastoreObjects.getGlueTestDatabase; import static io.trino.plugin.hive.metastore.glue.v1.TestingMetastoreObjects.getGlueTestPartition; import static io.trino.plugin.hive.metastore.glue.v1.TestingMetastoreObjects.getGlueTestStorageDescriptor; import static io.trino.plugin.hive.metastore.glue.v1.TestingMetastoreObjects.getGlueTestTable; import static io.trino.plugin.hive.metastore.glue.v1.TestingMetastoreObjects.getGlueTestTrinoMaterializedView; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getPartitionParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getStorageDescriptor; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableTypeNullable; 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; 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 5503de707e43..080f685864d4 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 @@ -49,6 +49,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -164,6 +165,12 @@ public List getTableMeta(String databaseName) return ImmutableList.of(new TableMeta(TEST_DATABASE, TEST_TABLE, MANAGED_TABLE.name())); } + @Override + public List getTableNamesWithParameters(String databaseName, String parameterKey, Set parameterValues) + { + throw new UnsupportedOperationException(); + } + @Override public Database getDatabase(String name) throws TException diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestBridgingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestBridgingHiveMetastore.java index 3c92e0f43aaa..7184f5732c51 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestBridgingHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestBridgingHiveMetastore.java @@ -15,10 +15,10 @@ import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.SchemaAlreadyExistsException; import io.trino.metastore.Table; +import io.trino.metastore.TableAlreadyExistsException; import io.trino.plugin.base.util.AutoCloseableCloser; -import io.trino.plugin.hive.SchemaAlreadyExistsException; -import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.containers.HiveHadoop; import io.trino.plugin.hive.metastore.AbstractTestHiveMetastore; import org.junit.jupiter.api.AfterAll; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreCatalogs.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreCatalogs.java index 1fa641e51955..11ab15937fa5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreCatalogs.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreCatalogs.java @@ -17,10 +17,10 @@ import io.trino.Session; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.HiveQueryRunner; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.containers.HiveHadoop; -import io.trino.plugin.hive.containers.HiveMinioDataLake; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.security.PrincipalType; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; @@ -52,7 +52,7 @@ protected QueryRunner createQueryRunner() throws Exception { this.bucketName = "test-hive-metastore-catalogs-" + randomNameSuffix(); - HiveMinioDataLake hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName, HiveHadoop.HIVE3_IMAGE)); + Hive3MinioDataLake hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName, HiveHadoop.HIVE3_IMAGE)); hiveMinioDataLake.start(); QueryRunner queryRunner = HiveQueryRunner.builder() @@ -75,11 +75,11 @@ protected QueryRunner createQueryRunner() return queryRunner; } - private static Map buildHiveProperties(HiveMinioDataLake hiveMinioDataLake) + private static Map buildHiveProperties(Hive3MinioDataLake hiveMinioDataLake) { return ImmutableMap.builder() .put("hive.metastore", "thrift") - .put("hive.metastore.uri", hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint().toString()) + .put("hive.metastore.uri", hiveMinioDataLake.getHiveMetastoreEndpoint().toString()) .put("fs.hadoop.enabled", "false") .put("fs.native-s3.enabled", "true") .put("s3.path-style-access", "true") 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 0c6bbcf0d0d9..aa8fcba701c5 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 @@ -21,11 +21,11 @@ import io.trino.metastore.Column; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.HiveType; import io.trino.metastore.Table; import io.trino.plugin.hive.HiveQueryRunner; import io.trino.plugin.hive.containers.HiveHadoop; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.MetastoreMethod; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftHiveMetastoreClient.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftHiveMetastoreClient.java index c469374be025..6faa578b65ab 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftHiveMetastoreClient.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftHiveMetastoreClient.java @@ -50,6 +50,7 @@ public void testAlternativeCall() true, new AtomicInteger(), new AtomicInteger(), + new AtomicInteger(), new AtomicInteger()); assertThat(connectionCount.get()).isEqualTo(1); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestUnityMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestUnityMetastore.java index 16afc1bad2b6..c3799770bd74 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestUnityMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestUnityMetastore.java @@ -27,7 +27,7 @@ import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; import static io.trino.plugin.hive.metastore.thrift.ThriftHttpMetastoreConfig.AuthenticationMode.BEARER; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static org.assertj.core.api.Assertions.assertThat; final class TestUnityMetastore @@ -36,9 +36,9 @@ final class TestUnityMetastore void test() throws Exception { - String databricksHost = requireNonNull(System.getenv("DATABRICKS_HOST"), "Environment variable not set: DATABRICKS_HOST"); - String databricksToken = requireNonNull(System.getenv("DATABRICKS_TOKEN"), "Environment variable not set: DATABRICKS_TOKEN"); - String databricksCatalogName = requireNonNull(System.getenv("DATABRICKS_UNITY_CATALOG_NAME"), "Environment variable not set: DATABRICKS_UNITY_CATALOG_NAME"); + String databricksHost = requireEnv("DATABRICKS_HOST"); + String databricksToken = requireEnv("DATABRICKS_TOKEN"); + String databricksCatalogName = requireEnv("DATABRICKS_UNITY_CATALOG_NAME"); URI metastoreUri = URI.create("https://%s:443/api/2.0/unity-hms-proxy/metadata" .formatted(databricksHost)); ThriftHttpMetastoreConfig config = new ThriftHttpMetastoreConfig() diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestConnectorPushdownRulesWithHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestConnectorPushdownRulesWithHive.java index 844787214493..25b6b48853c3 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestConnectorPushdownRulesWithHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestConnectorPushdownRulesWithHive.java @@ -23,12 +23,12 @@ import io.trino.metadata.TestingFunctionResolution; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HiveColumnProjectionInfo; import io.trino.plugin.hive.HiveTableHandle; import io.trino.plugin.hive.HiveTransactionHandle; import io.trino.plugin.hive.TestingHiveConnectorFactory; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.connector.CatalogHandle; import io.trino.spi.function.OperatorType; import io.trino.spi.predicate.Domain; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestHivePlans.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestHivePlans.java index 61af8171ee8a..e177d5415f39 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestHivePlans.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestHivePlans.java @@ -20,8 +20,8 @@ import io.trino.metadata.TestingFunctionResolution; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.TestingHiveConnectorFactory; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.function.OperatorType; import io.trino.spi.security.PrincipalType; import io.trino.sql.ir.Between; 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 478cdc257609..fa6499bce512 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 @@ -23,10 +23,10 @@ import io.trino.metadata.TestingFunctionResolution; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HiveTableHandle; import io.trino.plugin.hive.TestingHiveConnectorFactory; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.connector.ColumnHandle; import io.trino.spi.function.OperatorType; import io.trino.spi.predicate.Domain; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestHiveOrcWithShortZoneId.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestHiveOrcWithShortZoneId.java index 8ab4dc0c8ba5..b450e3950324 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestHiveOrcWithShortZoneId.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestHiveOrcWithShortZoneId.java @@ -63,8 +63,7 @@ protected QueryRunner createQueryRunner() public void testSelectWithShortZoneId() { // When table is created using ORC file that contains short zone id in stripe footer - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_select_with_short_zone_id_", "(id INT, firstName VARCHAR, lastName VARCHAR) WITH (external_location = '%s')".formatted(dataFile.parentDirectory()))) { assertQuery("SELECT * FROM " + testTable.getName(), "VALUES (1, 'John', 'Doe')"); @@ -75,8 +74,7 @@ public void testSelectWithShortZoneId() public void testSelectWithoutShortZoneId() { // When table is created by trino - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_select_without_short_zone_id_", "(id INT, firstName VARCHAR, lastName VARCHAR)", ImmutableList.of("2, 'Alice', 'Doe'"))) { 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 9df3a85934b3..ea246e127f5d 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 @@ -19,6 +19,8 @@ import io.airlift.log.Logging; import io.airlift.units.Duration; import io.trino.plugin.hive.HiveQueryRunner; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; +import io.trino.plugin.hive.containers.Hive4MinioDataLake; import io.trino.plugin.hive.containers.HiveMinioDataLake; import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; import io.trino.plugin.hive.metastore.thrift.TestingTokenAwareMetastoreClientFactory; @@ -47,7 +49,7 @@ public final class S3HiveQueryRunner private S3HiveQueryRunner() {} public static QueryRunner create( - HiveMinioDataLake hiveMinioDataLake, + Hive3MinioDataLake hiveMinioDataLake, Map additionalHiveProperties) throws Exception { @@ -59,7 +61,7 @@ public static QueryRunner create( public static Builder builder(HiveMinioDataLake hiveMinioDataLake) { return builder() - .setHiveMetastoreEndpoint(hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint()) + .setHiveMetastoreEndpoint(hiveMinioDataLake.getHiveMetastoreEndpoint()) .setS3Endpoint("http://" + hiveMinioDataLake.getMinio().getMinioApiEndpoint()) .setS3Region(MINIO_REGION) .setS3AccessKey(MINIO_ACCESS_KEY) @@ -173,7 +175,7 @@ public DistributedQueryRunner build() public static void main(String[] args) throws Exception { - HiveMinioDataLake hiveMinioDataLake = new HiveMinioDataLake("tpch"); + Hive3MinioDataLake hiveMinioDataLake = new Hive3MinioDataLake("tpch"); hiveMinioDataLake.start(); QueryRunner queryRunner = S3HiveQueryRunner.builder(hiveMinioDataLake) @@ -186,4 +188,24 @@ public static void main(String[] args) log.info("======== SERVER STARTED ========"); log.info("\n====\n%s\n====", queryRunner.getCoordinator().getBaseUrl()); } + + public static class S3Hive4QueryRunner + { + public static void main(String[] args) + throws Exception + { + Hive4MinioDataLake hiveMinioDataLake = new Hive4MinioDataLake("tpch"); + hiveMinioDataLake.start(); + + QueryRunner queryRunner = S3HiveQueryRunner.builder(hiveMinioDataLake) + .addCoordinatorProperty("http-server.http.port", "8080") + .setHiveProperties(ImmutableMap.of("hive.security", "allow-all")) + .setSkipTimezoneSetup(true) + .setInitialTables(TpchTable.getTables()) + .build(); + Logger log = Logger.get(S3Hive4QueryRunner.class); + log.info("======== SERVER STARTED ========"); + log.info("\n====\n%s\n====", queryRunner.getCoordinator().getBaseUrl()); + } + } } 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 8c0fbaf5b091..8c8d6c172a1a 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 @@ -14,7 +14,7 @@ package io.trino.plugin.hive.s3; import com.google.common.collect.ImmutableMap; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; import org.junit.jupiter.api.Test; @@ -31,7 +31,7 @@ public class TestHiveS3MinioQueries extends AbstractTestQueryFramework { - private HiveMinioDataLake hiveMinioDataLake; + private Hive3MinioDataLake hiveMinioDataLake; private String bucketName; @Override @@ -39,7 +39,7 @@ protected QueryRunner createQueryRunner() throws Exception { this.bucketName = "test-hive-minio-queries-" + randomNameSuffix(); - this.hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + this.hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); this.hiveMinioDataLake.start(); return S3HiveQueryRunner.builder(hiveMinioDataLake) 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 61845ce0bac8..f74ea30fb771 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 @@ -53,11 +53,11 @@ import static io.trino.metastore.HivePartition.UNPARTITIONED_ID; import static io.trino.metastore.HiveType.HIVE_LONG; import static io.trino.metastore.HiveType.HIVE_STRING; +import static io.trino.metastore.Partitions.HIVE_DEFAULT_DYNAMIC_PARTITION; 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.HiveErrorCode.HIVE_CORRUPTED_COLUMN_STATISTICS; -import static io.trino.plugin.hive.HivePartitionKey.HIVE_DEFAULT_DYNAMIC_PARTITION; import static io.trino.plugin.hive.HivePartitionManager.parsePartition; import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; 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 6630f9b96858..b4b80d360c56 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 @@ -13,20 +13,11 @@ */ package io.trino.plugin.hive.util; -import io.trino.metastore.Partition; -import org.apache.hadoop.hive.common.FileUtils; -import org.apache.hadoop.hive.metastore.Warehouse; -import org.apache.hadoop.hive.metastore.api.MetaException; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import org.junit.jupiter.api.Test; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.List; - -import static io.trino.metastore.Partition.toPartitionValues; import static io.trino.plugin.hive.util.HiveUtil.escapeSchemaName; import static io.trino.plugin.hive.util.HiveUtil.escapeTableName; import static io.trino.plugin.hive.util.HiveUtil.parseHiveTimestamp; @@ -47,43 +38,6 @@ public void testParseHiveTimestamp() assertThat(parse(time, "yyyy-MM-dd HH:mm:ss.SSSSSSSSS")).isEqualTo(unixTime(time, 7)); } - @Test - public void testToPartitionValues() - throws MetaException - { - assertToPartitionValues("ds=2015-12-30/event_type=QueryCompletion"); - assertToPartitionValues("ds=2015-12-30"); - assertToPartitionValues("a=1/b=2/c=3"); - assertToPartitionValues("a=1"); - assertToPartitionValues("pk=!@%23$%25%5E&%2A()%2F%3D"); - assertToPartitionValues("pk=__HIVE_DEFAULT_PARTITION__"); - } - - @Test - public void testUnescapePathName() - { - assertUnescapePathName("", ""); - assertUnescapePathName("x", "x"); - assertUnescapePathName("abc", "abc"); - assertUnescapePathName("abc%", "abc%"); - assertUnescapePathName("%", "%"); - assertUnescapePathName("%41", "A"); - assertUnescapePathName("%41%x", "A%x"); - assertUnescapePathName("%41%xxZ", "A%xxZ"); - assertUnescapePathName("%41%%Z", "A%%Z"); - assertUnescapePathName("%41%25%25Z", "A%%Z"); - assertUnescapePathName("abc%41%42%43", "abcABC"); - assertUnescapePathName("abc%3Axyz", "abc:xyz"); - assertUnescapePathName("abc%3axyz", "abc:xyz"); - assertUnescapePathName("abc%BBxyz", "abc\u00BBxyz"); - } - - private static void assertUnescapePathName(String value, String expected) - { - assertThat(FileUtils.unescapePathName(value)).isEqualTo(expected); - assertThat(Partition.unescapePathName(value)).isEqualTo(expected); - } - @Test public void testEscapeDatabaseName() { @@ -108,56 +62,6 @@ public void testEscapeTableName() assertThat(escapeTableName("../../table1")).isEqualTo("..%2F..%2Ftable1"); } - @Test - public void testEscapePathName() - { - assertEscapePathName(null, "__HIVE_DEFAULT_PARTITION__"); - assertEscapePathName("", "__HIVE_DEFAULT_PARTITION__"); - assertEscapePathName("x", "x"); - assertEscapePathName("abc", "abc"); - assertEscapePathName("%", "%25"); - assertEscapePathName("A", "A"); - assertEscapePathName("A%x", "A%25x"); - assertEscapePathName("A%xxZ", "A%25xxZ"); - assertEscapePathName("A%%Z", "A%25%25Z"); - assertEscapePathName("abcABC", "abcABC"); - assertEscapePathName("abc:xyz", "abc%3Axyz"); - assertEscapePathName("abc\u00BBxyz", "abc\u00BBxyz"); - assertEscapePathName("\u0000\t\b\r\n\u001F", "%00%09%08%0D%0A%1F"); - assertEscapePathName("#%^&*=[]{\\:'\"/?", "%23%25%5E&%2A%3D%5B%5D%7B%5C%3A%27%22%2F%3F"); - assertEscapePathName("~`!@$()-_+}|;,.<>", "~`!@$()-_+}|;,.<>"); - } - - private static void assertEscapePathName(String value, String expected) - { - assertThat(FileUtils.escapePathName(value)).isEqualTo(expected); - assertThat(HiveUtil.escapePathName(value)).isEqualTo(expected); - } - - @Test - public void testMakePartName() - { - assertMakePartName(List.of("abc"), List.of("xyz"), "abc=xyz"); - assertMakePartName(List.of("abc:qqq"), List.of("xyz/yyy=zzz"), "abc%3Aqqq=xyz%2Fyyy%3Dzzz"); - assertMakePartName(List.of("abc", "def", "xyz"), List.of("qqq", "rrr", "sss"), "abc=qqq/def=rrr/xyz=sss"); - } - - private static void assertMakePartName(List columns, List values, String expected) - { - assertThat(FileUtils.makePartName(columns, values)).isEqualTo(expected); - assertThat(HiveUtil.makePartName(columns, values)).isEqualTo(expected); - } - - private static void assertToPartitionValues(String partitionName) - throws MetaException - { - List actual = toPartitionValues(partitionName); - AbstractList expected = new ArrayList<>(); - actual.forEach(s -> expected.add(null)); - Warehouse.makeValsFromName(partitionName, expected); - assertThat(actual).isEqualTo(expected); - } - private static long parse(DateTime time, String pattern) { return parseHiveTimestamp(DateTimeFormat.forPattern(pattern).print(time)); diff --git a/plugin/trino-hive/src/test/resources/hive_minio_datalake/hive4-hive-site.xml b/plugin/trino-hive/src/test/resources/hive_minio_datalake/hive4-hive-site.xml new file mode 100644 index 000000000000..eec52b5f6ac5 --- /dev/null +++ b/plugin/trino-hive/src/test/resources/hive_minio_datalake/hive4-hive-site.xml @@ -0,0 +1,68 @@ + + + + hive.server2.enable.doAs + false + + + hive.tez.exec.inplace.progress + false + + + hive.exec.scratchdir + /opt/hive/scratch_dir + + + hive.user.install.directory + /opt/hive/install_dir + + + tez.runtime.optimize.local.fetch + true + + + hive.exec.submit.local.task.via.child + false + + + mapreduce.framework.name + local + + + hive.metastore.warehouse.dir + /opt/hive/data/warehouse + + + metastore.metastore.event.db.notification.api.auth + false + + + + + hive.users.in.admin.role + hive + + + + + fs.s3a.access.key + accesskey + + + fs.s3a.secret.key + secretkey + + + fs.s3a.endpoint + http://minio:4566 + + + fs.s3a.path.style.access + true + + + fs.s3.impl + org.apache.hadoop.fs.s3a.S3AFileSystem + + + diff --git a/plugin/trino-http-event-listener/pom.xml b/plugin/trino-http-event-listener/pom.xml index c21ce86410a6..cc1044569593 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 - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-http-server-event-listener/pom.xml b/plugin/trino-http-server-event-listener/pom.xml index 29dc7f087d69..a77b7996c059 100644 --- a/plugin/trino-http-server-event-listener/pom.xml +++ b/plugin/trino-http-server-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index ed968a22aa45..f8eb77ab75cf 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 4a5fe257a598..444d20e6a833 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 @@ -15,14 +15,14 @@ import com.google.inject.Inject; import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.spi.security.ConnectorIdentity; import io.trino.spi.type.TypeManager; import java.util.Optional; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static java.util.Objects.requireNonNull; public class HudiMetadataFactory 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 bee386c71553..83d62c9ab160 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 @@ -70,6 +70,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; +import static io.trino.metastore.Partitions.makePartName; import static io.trino.parquet.ParquetTypeUtils.getColumnIO; import static io.trino.parquet.ParquetTypeUtils.getDescriptors; import static io.trino.parquet.predicate.PredicateUtils.buildPredicate; @@ -80,7 +81,6 @@ import static io.trino.plugin.hive.parquet.ParquetPageSourceFactory.createParquetPageSource; 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; import static io.trino.plugin.hudi.HudiErrorCode.HUDI_BAD_DATA; import static io.trino.plugin.hudi.HudiErrorCode.HUDI_CANNOT_OPEN_SPLIT; import static io.trino.plugin.hudi.HudiErrorCode.HUDI_CURSOR_ERROR; @@ -218,7 +218,7 @@ private static ConnectorPageSource createPageSource( start, length, dataSource, - parquetMetadata.getBlocks(), + parquetMetadata, ImmutableList.of(parquetTupleDomain), ImmutableList.of(parquetPredicate), descriptorsByPath, diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/partition/HiveHudiPartitionInfo.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/partition/HiveHudiPartitionInfo.java index 7efa635f4456..906be1436c0f 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/partition/HiveHudiPartitionInfo.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/partition/HiveHudiPartitionInfo.java @@ -29,6 +29,7 @@ import java.util.Optional; import static com.google.common.base.MoreObjects.toStringHelper; +import static io.trino.metastore.Partitions.toPartitionValues; import static io.trino.plugin.hudi.HudiErrorCode.HUDI_PARTITION_NOT_FOUND; import static io.trino.plugin.hudi.HudiUtil.buildPartitionKeys; import static io.trino.plugin.hudi.HudiUtil.partitionMatchesPredicates; @@ -72,7 +73,7 @@ public HiveHudiPartitionInfo( public String getRelativePartitionPath() { if (relativePartitionPath == null) { - loadPartitionInfo(hiveMetastore.getPartition(table, Partition.toPartitionValues(hivePartitionName))); + loadPartitionInfo(hiveMetastore.getPartition(table, toPartitionValues(hivePartitionName))); } return relativePartitionPath; } @@ -81,7 +82,7 @@ public String getRelativePartitionPath() public List getHivePartitionKeys() { if (hivePartitionKeys == null) { - loadPartitionInfo(hiveMetastore.getPartition(table, Partition.toPartitionValues(hivePartitionName))); + loadPartitionInfo(hiveMetastore.getPartition(table, toPartitionValues(hivePartitionName))); } return hivePartitionKeys; } 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 8f817c21fabf..e7ebda90a29e 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 @@ -19,9 +19,9 @@ import io.airlift.log.Logging; import io.trino.filesystem.Location; import io.trino.metastore.Database; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.base.util.Closables; -import io.trino.plugin.hive.containers.HiveMinioDataLake; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hudi.testing.HudiTablesInitializer; import io.trino.plugin.hudi.testing.ResourceHudiTablesInitializer; import io.trino.plugin.hudi.testing.TpchHudiTablesInitializer; @@ -56,7 +56,7 @@ public static Builder builder() return new Builder("local:///"); } - public static Builder builder(HiveMinioDataLake hiveMinioDataLake) + public static Builder builder(Hive3MinioDataLake hiveMinioDataLake) { return new Builder("s3://" + hiveMinioDataLake.getBucketName() + "/") .addConnectorProperty("fs.hadoop.enabled", "false") @@ -157,7 +157,7 @@ public static void main(String[] args) Logging.initialize(); Logger log = Logger.get(HudiMinioQueryRunnerMain.class); - HiveMinioDataLake hiveMinioDataLake = new HiveMinioDataLake("test-bucket"); + Hive3MinioDataLake hiveMinioDataLake = new Hive3MinioDataLake("test-bucket"); hiveMinioDataLake.start(); QueryRunner queryRunner = builder(hiveMinioDataLake) .addCoordinatorProperty("http-server.http.port", "8080") diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiMinioConnectorSmokeTest.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiMinioConnectorSmokeTest.java index 069fb05c0523..5fa1332073cd 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiMinioConnectorSmokeTest.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiMinioConnectorSmokeTest.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.hudi; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hudi.testing.TpchHudiTablesInitializer; import io.trino.testing.QueryRunner; @@ -29,7 +29,7 @@ protected QueryRunner createQueryRunner() throws Exception { String bucketName = "test-hudi-connector-" + randomNameSuffix(); - HiveMinioDataLake hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName, HIVE3_IMAGE)); + Hive3MinioDataLake hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName, HIVE3_IMAGE)); hiveMinioDataLake.start(); hiveMinioDataLake.getMinioClient().ensureBucketExists(bucketName); 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 47c7b195abeb..55d7c78b22fb 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 @@ -20,6 +20,7 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.metastore.Column; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.HiveType; import io.trino.metastore.Partition; import io.trino.metastore.PartitionStatistics; @@ -27,7 +28,6 @@ import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.StorageFormat; import io.trino.metastore.Table; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hudi.HudiConnector; import io.trino.spi.security.ConnectorIdentity; import io.trino.testing.QueryRunner; 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 fc9d04ced4a6..9987fdba387e 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 @@ -23,11 +23,11 @@ import io.trino.hdfs.HdfsEnvironment; import io.trino.metastore.Column; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.HiveType; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.StorageFormat; import io.trino.metastore.Table; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hudi.HudiConnector; import io.trino.plugin.tpch.TpchPlugin; import io.trino.spi.connector.CatalogSchemaName; diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 8849c30793ab..9fbd26c7f844 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -15,7 +15,7 @@ - 0.101.1 + 0.101.3 diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/EntriesTable.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/EntriesTable.java new file mode 100644 index 000000000000..9c0560800826 --- /dev/null +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/EntriesTable.java @@ -0,0 +1,271 @@ +/* + * 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.ImmutableList; +import io.trino.plugin.iceberg.util.PageListBuilder; +import io.trino.spi.block.ArrayBlockBuilder; +import io.trino.spi.block.MapBlockBuilder; +import io.trino.spi.block.RowBlockBuilder; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.connector.ConnectorTableMetadata; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.type.ArrayType; +import io.trino.spi.type.RowType; +import io.trino.spi.type.TimeZoneKey; +import io.trino.spi.type.TypeManager; +import io.trino.spi.type.TypeSignature; +import jakarta.annotation.Nullable; +import org.apache.iceberg.MetadataTableType; +import org.apache.iceberg.MetricsUtil.ReadableMetricsStruct; +import org.apache.iceberg.PartitionField; +import org.apache.iceberg.Table; +import org.apache.iceberg.transforms.Transforms; +import org.apache.iceberg.types.Conversions; +import org.apache.iceberg.types.Type; +import org.apache.iceberg.types.Types; +import org.apache.iceberg.util.StructProjection; + +import java.nio.ByteBuffer; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutorService; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.airlift.slice.Slices.wrappedHeapBuffer; +import static io.trino.plugin.iceberg.FilesTable.getIcebergIdToTypeMapping; +import static io.trino.plugin.iceberg.IcebergTypes.convertIcebergValueToTrino; +import static io.trino.plugin.iceberg.IcebergUtil.getPartitionColumnType; +import static io.trino.plugin.iceberg.IcebergUtil.partitionTypes; +import static io.trino.plugin.iceberg.IcebergUtil.primitiveFieldTypes; +import static io.trino.plugin.iceberg.PartitionsTable.getAllPartitionFields; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.StandardTypes.JSON; +import static io.trino.spi.type.TypeSignature.mapType; +import static io.trino.spi.type.TypeUtils.writeNativeValue; +import static io.trino.spi.type.VarbinaryType.VARBINARY; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static java.util.Objects.requireNonNull; +import static org.apache.iceberg.MetadataTableType.ALL_ENTRIES; +import static org.apache.iceberg.MetadataTableType.ENTRIES; + +// https://iceberg.apache.org/docs/latest/spark-queries/#all-entries +// https://iceberg.apache.org/docs/latest/spark-queries/#entries +public class EntriesTable + extends BaseSystemTable +{ + private final Map idToTypeMapping; + private final List primitiveFields; + private final Optional partitionColumn; + private final List partitionTypes; + + public EntriesTable(TypeManager typeManager, SchemaTableName tableName, Table icebergTable, MetadataTableType metadataTableType, ExecutorService executor) + { + super( + requireNonNull(icebergTable, "icebergTable is null"), + new ConnectorTableMetadata( + requireNonNull(tableName, "tableName is null"), + columns(requireNonNull(typeManager, "typeManager is null"), icebergTable)), + metadataTableType, + executor); + checkArgument(metadataTableType == ALL_ENTRIES || metadataTableType == ENTRIES, "Unexpected metadata table type: %s", metadataTableType); + idToTypeMapping = getIcebergIdToTypeMapping(icebergTable.schema()); + primitiveFields = IcebergUtil.primitiveFields(icebergTable.schema()).stream() + .sorted(Comparator.comparing(Types.NestedField::name)) + .collect(toImmutableList()); + List partitionFields = getAllPartitionFields(icebergTable); + partitionColumn = getPartitionColumnType(partitionFields, icebergTable.schema(), typeManager); + partitionTypes = partitionTypes(partitionFields, primitiveFieldTypes(icebergTable.schema())); + } + + private static List columns(TypeManager typeManager, Table icebergTable) + { + return ImmutableList.builder() + .add(new ColumnMetadata("status", INTEGER)) + .add(new ColumnMetadata("snapshot_id", BIGINT)) + .add(new ColumnMetadata("sequence_number", BIGINT)) + .add(new ColumnMetadata("file_sequence_number", BIGINT)) + .add(new ColumnMetadata("data_file", RowType.from(dataFileFieldMetadata(typeManager, icebergTable)))) + .add(new ColumnMetadata("readable_metrics", typeManager.getType(new TypeSignature(JSON)))) + .build(); + } + + private static List dataFileFieldMetadata(TypeManager typeManager, Table icebergTable) + { + List partitionFields = getAllPartitionFields(icebergTable); + Optional partitionColumnType = getPartitionColumnType(partitionFields, icebergTable.schema(), typeManager); + + ImmutableList.Builder fields = ImmutableList.builder(); + fields.add(new RowType.Field(Optional.of("content"), INTEGER)); + fields.add(new RowType.Field(Optional.of("file_path"), VARCHAR)); + fields.add(new RowType.Field(Optional.of("file_format"), VARCHAR)); + fields.add(new RowType.Field(Optional.of("spec_id"), INTEGER)); + partitionColumnType.ifPresent(type -> fields.add(new RowType.Field(Optional.of("partition"), type.rowType()))); + fields.add(new RowType.Field(Optional.of("record_count"), BIGINT)); + fields.add(new RowType.Field(Optional.of("file_size_in_bytes"), BIGINT)); + fields.add(new RowType.Field(Optional.of("column_sizes"), typeManager.getType(mapType(INTEGER.getTypeSignature(), BIGINT.getTypeSignature())))); + fields.add(new RowType.Field(Optional.of("value_counts"), typeManager.getType(mapType(INTEGER.getTypeSignature(), BIGINT.getTypeSignature())))); + fields.add(new RowType.Field(Optional.of("null_value_counts"), typeManager.getType(mapType(INTEGER.getTypeSignature(), BIGINT.getTypeSignature())))); + fields.add(new RowType.Field(Optional.of("nan_value_counts"), typeManager.getType(mapType(INTEGER.getTypeSignature(), BIGINT.getTypeSignature())))); + fields.add(new RowType.Field(Optional.of("lower_bounds"), typeManager.getType(mapType(INTEGER.getTypeSignature(), VARCHAR.getTypeSignature())))); + fields.add(new RowType.Field(Optional.of("upper_bounds"), typeManager.getType(mapType(INTEGER.getTypeSignature(), VARCHAR.getTypeSignature())))); + fields.add(new RowType.Field(Optional.of("key_metadata"), VARBINARY)); + fields.add(new RowType.Field(Optional.of("split_offsets"), new ArrayType(BIGINT))); + fields.add(new RowType.Field(Optional.of("equality_ids"), new ArrayType(INTEGER))); + fields.add(new RowType.Field(Optional.of("sort_order_id"), INTEGER)); + return fields.build(); + } + + @Override + protected void addRow(PageListBuilder pagesBuilder, Row row, TimeZoneKey timeZoneKey) + { + pagesBuilder.beginRow(); + pagesBuilder.appendInteger(row.get("status", Integer.class)); + pagesBuilder.appendBigint(row.get("snapshot_id", Long.class)); + pagesBuilder.appendBigint(row.get("sequence_number", Long.class)); + pagesBuilder.appendBigint(row.get("file_sequence_number", Long.class)); + StructProjection dataFile = row.get("data_file", StructProjection.class); + appendDataFile((RowBlockBuilder) pagesBuilder.nextColumn(), dataFile); + ReadableMetricsStruct readableMetrics = row.get("readable_metrics", ReadableMetricsStruct.class); + String readableMetricsJson = FilesTable.toJson(readableMetrics, primitiveFields); + pagesBuilder.appendVarchar(readableMetricsJson); + pagesBuilder.endRow(); + } + + private void appendDataFile(RowBlockBuilder blockBuilder, StructProjection dataFile) + { + blockBuilder.buildEntry(fieldBuilders -> { + Integer content = dataFile.get(0, Integer.class); + INTEGER.writeLong(fieldBuilders.get(0), content); + + String filePath = dataFile.get(1, String.class); + VARCHAR.writeString(fieldBuilders.get(1), filePath); + + String fileFormat = dataFile.get(2, String.class); + VARCHAR.writeString(fieldBuilders.get(2), fileFormat); + + Integer specId = dataFile.get(3, Integer.class); + INTEGER.writeLong(fieldBuilders.get(3), Long.valueOf(specId)); + + partitionColumn.ifPresent(type -> { + StructProjection partition = dataFile.get(4, StructProjection.class); + RowBlockBuilder partitionBlockBuilder = (RowBlockBuilder) fieldBuilders.get(4); + partitionBlockBuilder.buildEntry(partitionBuilder -> { + for (int i = 0; i < type.rowType().getFields().size(); i++) { + Type icebergType = partitionTypes.get(i); + io.trino.spi.type.Type trinoType = type.rowType().getFields().get(i).getType(); + Object value = null; + Integer fieldId = type.fieldIds().get(i); + if (fieldId != null) { + value = convertIcebergValueToTrino(icebergType, partition.get(i, icebergType.typeId().javaClass())); + } + writeNativeValue(trinoType, partitionBuilder.get(i), value); + } + }); + }); + + int position = partitionColumn.isEmpty() ? 4 : 5; + Long recordCount = dataFile.get(position, Long.class); + BIGINT.writeLong(fieldBuilders.get(position), recordCount); + + Long fileSizeInBytes = dataFile.get(++position, Long.class); + BIGINT.writeLong(fieldBuilders.get(position), fileSizeInBytes); + + //noinspection unchecked + Map columnSizes = dataFile.get(++position, Map.class); + appendIntegerBigintMap((MapBlockBuilder) fieldBuilders.get(position), columnSizes); + + //noinspection unchecked + Map valueCounts = dataFile.get(++position, Map.class); + appendIntegerBigintMap((MapBlockBuilder) fieldBuilders.get(position), valueCounts); + + //noinspection unchecked + Map nullValueCounts = dataFile.get(++position, Map.class); + appendIntegerBigintMap((MapBlockBuilder) fieldBuilders.get(position), nullValueCounts); + + //noinspection unchecked + Map nanValueCounts = dataFile.get(++position, Map.class); + appendIntegerBigintMap((MapBlockBuilder) fieldBuilders.get(position), nanValueCounts); + + //noinspection unchecked + Map lowerBounds = dataFile.get(++position, Map.class); + appendIntegerVarcharMap((MapBlockBuilder) fieldBuilders.get(position), lowerBounds); + + //noinspection unchecked + Map upperBounds = dataFile.get(++position, Map.class); + appendIntegerVarcharMap((MapBlockBuilder) fieldBuilders.get(position), upperBounds); + + ByteBuffer keyMetadata = dataFile.get(++position, ByteBuffer.class); + if (keyMetadata == null) { + fieldBuilders.get(position).appendNull(); + } + else { + VARBINARY.writeSlice(fieldBuilders.get(position), wrappedHeapBuffer(keyMetadata)); + } + + //noinspection unchecked + List splitOffsets = dataFile.get(++position, List.class); + appendBigintArray((ArrayBlockBuilder) fieldBuilders.get(position), splitOffsets); + + //noinspection unchecked + List equalityIds = dataFile.get(++position, List.class); + appendBigintArray((ArrayBlockBuilder) fieldBuilders.get(position), equalityIds); + + Integer sortOrderId = dataFile.get(++position, Integer.class); + INTEGER.writeLong(fieldBuilders.get(position), Long.valueOf(sortOrderId)); + }); + } + + public static void appendBigintArray(ArrayBlockBuilder blockBuilder, @Nullable List values) + { + if (values == null) { + blockBuilder.appendNull(); + return; + } + blockBuilder.buildEntry(elementBuilder -> { + for (Long value : values) { + BIGINT.writeLong(elementBuilder, value); + } + }); + } + + private static void appendIntegerBigintMap(MapBlockBuilder blockBuilder, @Nullable Map values) + { + if (values == null) { + blockBuilder.appendNull(); + return; + } + blockBuilder.buildEntry((keyBuilder, valueBuilder) -> values.forEach((key, value) -> { + INTEGER.writeLong(keyBuilder, key); + BIGINT.writeLong(valueBuilder, value); + })); + } + + private void appendIntegerVarcharMap(MapBlockBuilder blockBuilder, @Nullable Map values) + { + if (values == null) { + blockBuilder.appendNull(); + return; + } + blockBuilder.buildEntry((keyBuilder, valueBuilder) -> values.forEach((key, value) -> { + Type type = idToTypeMapping.get(key); + INTEGER.writeLong(keyBuilder, key); + VARCHAR.writeString(valueBuilder, Transforms.identity().toHumanString(type, Conversions.fromByteBuffer(type, value))); + })); + } +} 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 eb647f10fe3d..f63fc00bb9c8 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 @@ -52,7 +52,7 @@ public final class ExpressionConverter { private ExpressionConverter() {} - public static boolean isConvertableToIcebergExpression(Domain domain) + public static boolean isConvertibleToIcebergExpression(Domain domain) { if (isStructuralType(domain.getType())) { // structural types cannot be used to filter a table scan in Iceberg library. @@ -81,7 +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); + checkArgument(isConvertibleToIcebergExpression(domain), "Unexpected not convertible 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/FilesTable.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/FilesTable.java index 715941c12f1f..319a1376b209 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/FilesTable.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/FilesTable.java @@ -356,80 +356,12 @@ private List getRecord(StructLike structLike) columns.add(structLike.get(columnNameToPosition.get(SORT_ORDER_ID_COLUMN_NAME), Integer.class)); ReadableMetricsStruct readableMetrics = structLike.get(columnNameToPosition.get(READABLE_METRICS_COLUMN_NAME), ReadableMetricsStruct.class); - columns.add(toJson(readableMetrics)); + columns.add(toJson(readableMetrics, primitiveFields)); checkArgument(columns.size() == types.size(), "Expected %s types in row, but got %s values", types.size(), columns.size()); return columns; } - private String toJson(ReadableMetricsStruct readableMetrics) - { - StringWriter writer = new StringWriter(); - try { - JsonGenerator generator = JSON_FACTORY.createGenerator(writer); - generator.writeStartObject(); - - for (int i = 0; i < readableMetrics.size(); i++) { - Types.NestedField field = primitiveFields.get(i); - generator.writeFieldName(field.name()); - - generator.writeStartObject(); - ReadableColMetricsStruct columnMetrics = readableMetrics.get(i, ReadableColMetricsStruct.class); - - generator.writeFieldName("column_size"); - Long columnSize = columnMetrics.get(0, Long.class); - if (columnSize == null) { - generator.writeNull(); - } - else { - generator.writeNumber(columnSize); - } - - generator.writeFieldName("value_count"); - Long valueCount = columnMetrics.get(1, Long.class); - if (valueCount == null) { - generator.writeNull(); - } - else { - generator.writeNumber(valueCount); - } - - generator.writeFieldName("null_value_count"); - Long nullValueCount = columnMetrics.get(2, Long.class); - if (nullValueCount == null) { - generator.writeNull(); - } - else { - generator.writeNumber(nullValueCount); - } - - generator.writeFieldName("nan_value_count"); - Long nanValueCount = columnMetrics.get(3, Long.class); - if (nanValueCount == null) { - generator.writeNull(); - } - else { - generator.writeNumber(nanValueCount); - } - - generator.writeFieldName("lower_bound"); - SingleValueParser.toJson(field.type(), columnMetrics.get(4, Object.class), generator); - - generator.writeFieldName("upper_bound"); - SingleValueParser.toJson(field.type(), columnMetrics.get(5, Object.class), generator); - - generator.writeEndObject(); - } - - generator.writeEndObject(); - generator.flush(); - return writer.toString(); - } - catch (IOException e) { - throw new UncheckedIOException("JSON conversion failed for: " + readableMetrics, e); - } - } - private SqlMap getIntegerBigintSqlMap(Map value) { if (value == null) { @@ -506,7 +438,75 @@ private static Slice toVarbinarySlice(ByteBuffer value) } } - private static Map getIcebergIdToTypeMapping(Schema schema) + static String toJson(ReadableMetricsStruct readableMetrics, List primitiveFields) + { + StringWriter writer = new StringWriter(); + try { + JsonGenerator generator = JSON_FACTORY.createGenerator(writer); + generator.writeStartObject(); + + for (int i = 0; i < readableMetrics.size(); i++) { + Types.NestedField field = primitiveFields.get(i); + generator.writeFieldName(field.name()); + + generator.writeStartObject(); + ReadableColMetricsStruct columnMetrics = readableMetrics.get(i, ReadableColMetricsStruct.class); + + generator.writeFieldName("column_size"); + Long columnSize = columnMetrics.get(0, Long.class); + if (columnSize == null) { + generator.writeNull(); + } + else { + generator.writeNumber(columnSize); + } + + generator.writeFieldName("value_count"); + Long valueCount = columnMetrics.get(1, Long.class); + if (valueCount == null) { + generator.writeNull(); + } + else { + generator.writeNumber(valueCount); + } + + generator.writeFieldName("null_value_count"); + Long nullValueCount = columnMetrics.get(2, Long.class); + if (nullValueCount == null) { + generator.writeNull(); + } + else { + generator.writeNumber(nullValueCount); + } + + generator.writeFieldName("nan_value_count"); + Long nanValueCount = columnMetrics.get(3, Long.class); + if (nanValueCount == null) { + generator.writeNull(); + } + else { + generator.writeNumber(nanValueCount); + } + + generator.writeFieldName("lower_bound"); + SingleValueParser.toJson(field.type(), columnMetrics.get(4, Object.class), generator); + + generator.writeFieldName("upper_bound"); + SingleValueParser.toJson(field.type(), columnMetrics.get(5, Object.class), generator); + + generator.writeEndObject(); + } + + generator.writeEndObject(); + generator.flush(); + return writer.toString(); + } + catch (IOException e) { + throw new UncheckedIOException("JSON conversion failed for: " + readableMetrics, e); + } + } + + static Map getIcebergIdToTypeMapping(Schema schema) { ImmutableMap.Builder icebergIdToTypeMapping = ImmutableMap.builder(); for (Types.NestedField field : schema.columns()) { 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 2a6af35d5c1d..2b01414e1c10 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 @@ -36,6 +36,7 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.metastore.Column; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.TableInfo; import io.trino.plugin.base.classloader.ClassLoaderSafeSystemTable; import io.trino.plugin.base.filter.UtcConstraintExtractor; @@ -43,7 +44,6 @@ import io.trino.plugin.base.projection.ApplyProjectionUtil.ProjectedColumnRepresentation; import io.trino.plugin.hive.HiveStorageFormat; import io.trino.plugin.hive.HiveWrittenPartitions; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.iceberg.aggregation.DataSketchStateSerializer; import io.trino.plugin.iceberg.aggregation.IcebergThetaSketchForStats; import io.trino.plugin.iceberg.catalog.TrinoCatalog; @@ -133,6 +133,7 @@ import org.apache.iceberg.DataFiles; import org.apache.iceberg.DeleteFile; import org.apache.iceberg.DeleteFiles; +import org.apache.iceberg.FileFormat; import org.apache.iceberg.FileMetadata; import org.apache.iceberg.FileScanTask; import org.apache.iceberg.IsolationLevel; @@ -199,7 +200,10 @@ import java.util.Optional; import java.util.OptionalLong; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -228,6 +232,7 @@ 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.ExecutorUtil.processWithAdditionalThreads; import static io.trino.plugin.base.util.Procedures.checkProcedureArgument; import static io.trino.plugin.hive.HiveMetadata.TRANSACTIONAL; import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; @@ -237,7 +242,7 @@ import static io.trino.plugin.hive.util.HiveUtil.isHudiTable; import static io.trino.plugin.hive.util.HiveUtil.isIcebergTable; import static io.trino.plugin.iceberg.ColumnIdentity.createColumnIdentity; -import static io.trino.plugin.iceberg.ExpressionConverter.isConvertableToIcebergExpression; +import static io.trino.plugin.iceberg.ExpressionConverter.isConvertibleToIcebergExpression; 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,12 +282,15 @@ 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.OBJECT_STORE_LAYOUT_ENABLED_PROPERTY; +import static io.trino.plugin.iceberg.IcebergTableProperties.ORC_BLOOM_FILTER_COLUMNS_PROPERTY; +import static io.trino.plugin.iceberg.IcebergTableProperties.PARQUET_BLOOM_FILTER_COLUMNS_PROPERTY; import static io.trino.plugin.iceberg.IcebergTableProperties.PARTITIONING_PROPERTY; import static io.trino.plugin.iceberg.IcebergTableProperties.SORTED_BY_PROPERTY; import static io.trino.plugin.iceberg.IcebergTableProperties.getPartitioning; import static io.trino.plugin.iceberg.IcebergTableProperties.getTableLocation; import static io.trino.plugin.iceberg.IcebergUtil.buildPath; import static io.trino.plugin.iceberg.IcebergUtil.canEnforceColumnConstraintInSpecs; +import static io.trino.plugin.iceberg.IcebergUtil.checkFormatForProperty; import static io.trino.plugin.iceberg.IcebergUtil.commit; import static io.trino.plugin.iceberg.IcebergUtil.createColumnHandle; import static io.trino.plugin.iceberg.IcebergUtil.deserializePartitionValue; @@ -300,6 +308,8 @@ import static io.trino.plugin.iceberg.IcebergUtil.getTopLevelColumns; import static io.trino.plugin.iceberg.IcebergUtil.newCreateTableTransaction; import static io.trino.plugin.iceberg.IcebergUtil.schemaFromMetadata; +import static io.trino.plugin.iceberg.IcebergUtil.validateOrcBloomFilterColumns; +import static io.trino.plugin.iceberg.IcebergUtil.validateParquetBloomFilterColumns; import static io.trino.plugin.iceberg.IcebergUtil.verifyExtraProperties; import static io.trino.plugin.iceberg.PartitionFields.parsePartitionFields; import static io.trino.plugin.iceberg.SortFieldUtils.parseSortFields; @@ -354,6 +364,8 @@ import static java.util.Objects.requireNonNull; import static java.util.function.Function.identity; import static java.util.stream.Collectors.joining; +import static org.apache.iceberg.MetadataTableType.ALL_ENTRIES; +import static org.apache.iceberg.MetadataTableType.ENTRIES; import static org.apache.iceberg.ReachableFileUtil.metadataFileLocations; import static org.apache.iceberg.ReachableFileUtil.statisticsFilesLocations; import static org.apache.iceberg.SnapshotSummary.DELETED_RECORDS_PROP; @@ -364,6 +376,8 @@ import static org.apache.iceberg.TableProperties.DELETE_ISOLATION_LEVEL_DEFAULT; import static org.apache.iceberg.TableProperties.FORMAT_VERSION; import static org.apache.iceberg.TableProperties.OBJECT_STORE_ENABLED; +import static org.apache.iceberg.TableProperties.ORC_BLOOM_FILTER_COLUMNS; +import static org.apache.iceberg.TableProperties.PARQUET_BLOOM_FILTER_COLUMN_ENABLED_PREFIX; import static org.apache.iceberg.TableProperties.WRITE_DATA_LOCATION; import static org.apache.iceberg.TableProperties.WRITE_LOCATION_PROVIDER_IMPL; import static org.apache.iceberg.expressions.Expressions.alwaysTrue; @@ -386,6 +400,8 @@ public class IcebergMetadata .add(FORMAT_VERSION_PROPERTY) .add(OBJECT_STORE_LAYOUT_ENABLED_PROPERTY) .add(DATA_LOCATION_PROPERTY) + .add(ORC_BLOOM_FILTER_COLUMNS_PROPERTY) + .add(PARQUET_BLOOM_FILTER_COLUMNS_PROPERTY) .add(PARTITIONING_PROPERTY) .add(SORTED_BY_PROPERTY) .build(); @@ -406,7 +422,8 @@ public class IcebergMetadata private final Optional metastoreFactory; private final boolean addFilesProcedureEnabled; private final Predicate allowedExtraProperties; - private final ExecutorService executor; + private final ExecutorService icebergScanExecutor; + private final Executor metadataFetchingExecutor; private final Map> tableStatisticsCache = new ConcurrentHashMap<>(); @@ -423,7 +440,8 @@ public IcebergMetadata( Optional metastoreFactory, boolean addFilesProcedureEnabled, Predicate allowedExtraProperties, - ExecutorService executor) + ExecutorService icebergScanExecutor, + Executor metadataFetchingExecutor) { this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.trinoCatalogHandle = requireNonNull(trinoCatalogHandle, "trinoCatalogHandle is null"); @@ -434,7 +452,8 @@ public IcebergMetadata( this.metastoreFactory = requireNonNull(metastoreFactory, "metastoreFactory is null"); this.addFilesProcedureEnabled = addFilesProcedureEnabled; this.allowedExtraProperties = requireNonNull(allowedExtraProperties, "allowedExtraProperties is null"); - this.executor = requireNonNull(executor, "executor is null"); + this.icebergScanExecutor = requireNonNull(icebergScanExecutor, "icebergScanExecutor is null"); + this.metadataFetchingExecutor = requireNonNull(metadataFetchingExecutor, "metadataFetchingExecutor is null"); } @Override @@ -694,14 +713,16 @@ private Optional getRawSystemTable(ConnectorSession session, Schema return switch (tableType) { case DATA, MATERIALIZED_VIEW_STORAGE -> throw new VerifyException("Unexpected table type: " + tableType); // Handled above. case HISTORY -> Optional.of(new HistoryTable(tableName, table)); - case METADATA_LOG_ENTRIES -> Optional.of(new MetadataLogEntriesTable(tableName, table, executor)); - case SNAPSHOTS -> Optional.of(new SnapshotsTable(tableName, typeManager, table, executor)); - case PARTITIONS -> Optional.of(new PartitionsTable(tableName, typeManager, table, getCurrentSnapshotId(table), executor)); - case ALL_MANIFESTS -> Optional.of(new AllManifestsTable(tableName, table, executor)); + case METADATA_LOG_ENTRIES -> Optional.of(new MetadataLogEntriesTable(tableName, table, icebergScanExecutor)); + case SNAPSHOTS -> Optional.of(new SnapshotsTable(tableName, typeManager, table, icebergScanExecutor)); + case PARTITIONS -> Optional.of(new PartitionsTable(tableName, typeManager, table, getCurrentSnapshotId(table), icebergScanExecutor)); + case ALL_MANIFESTS -> Optional.of(new AllManifestsTable(tableName, table, icebergScanExecutor)); case MANIFESTS -> Optional.of(new ManifestsTable(tableName, table, getCurrentSnapshotId(table))); - case FILES -> Optional.of(new FilesTable(tableName, typeManager, table, getCurrentSnapshotId(table), executor)); + case FILES -> Optional.of(new FilesTable(tableName, typeManager, table, getCurrentSnapshotId(table), icebergScanExecutor)); + case ALL_ENTRIES -> Optional.of(new EntriesTable(typeManager, tableName, table, ALL_ENTRIES, icebergScanExecutor)); + case ENTRIES -> Optional.of(new EntriesTable(typeManager, tableName, table, ENTRIES, icebergScanExecutor)); case PROPERTIES -> Optional.of(new PropertiesTable(tableName, table)); - case REFS -> Optional.of(new RefsTable(tableName, table, executor)); + case REFS -> Optional.of(new RefsTable(tableName, table, icebergScanExecutor)); }; } @@ -981,23 +1002,40 @@ public Iterator streamTableColumns(ConnectorSession sessio tableMetadatas.add(TableColumnsMetadata.forTable(tableName, columns)); }); - for (SchemaTableName tableName : remainingTables) { - try { - Table icebergTable = catalog.loadTable(session, tableName); - List columns = getColumnMetadatas(icebergTable.schema(), typeManager); - tableMetadatas.add(TableColumnsMetadata.forTable(tableName, columns)); - } - catch (TableNotFoundException e) { - // Table disappeared during listing operation - } - catch (UnknownTableTypeException e) { - // Skip unsupported table type in case that the table redirects are not enabled - } - catch (RuntimeException e) { - // Table can be being removed and this may cause all sorts of exceptions. Log, because we're catching broadly. - log.warn(e, "Failed to access metadata of table %s during streaming table columns for %s", tableName, prefix); - } + List>> tasks = remainingTables.stream() + .map(tableName -> (Callable>) () -> { + try { + Table icebergTable = catalog.loadTable(session, tableName); + List columns = getColumnMetadatas(icebergTable.schema(), typeManager); + return Optional.of(TableColumnsMetadata.forTable(tableName, columns)); + } + catch (TableNotFoundException e) { + // Table disappeared during listing operation + return Optional.empty(); + } + catch (UnknownTableTypeException e) { + // Skip unsupported table type in case that the table redirects are not enabled + return Optional.empty(); + } + catch (RuntimeException e) { + // Table can be being removed and this may cause all sorts of exceptions. Log, because we're catching broadly. + log.warn(e, "Failed to access metadata of table %s during streaming table columns for %s", tableName, prefix); + return Optional.empty(); + } + }) + .collect(toImmutableList()); + + try { + List taskResults = processWithAdditionalThreads(tasks, metadataFetchingExecutor).stream() + .flatMap(Optional::stream) // Flatten the Optionals into a stream + .collect(toImmutableList()); + + tableMetadatas.addAll(taskResults); + } + catch (ExecutionException e) { + throw new RuntimeException(e.getCause()); } + return tableMetadatas.build(); }) .flatMap(List::stream) @@ -2245,6 +2283,36 @@ public void setTableProperties(ConnectorSession session, ConnectorTableHandle ta extraProperties.forEach(updateProperties::set); } + if (properties.containsKey(PARQUET_BLOOM_FILTER_COLUMNS_PROPERTY)) { + checkFormatForProperty(getFileFormat(icebergTable).toIceberg(), FileFormat.PARQUET, PARQUET_BLOOM_FILTER_COLUMNS_PROPERTY); + //noinspection unchecked + List parquetBloomFilterColumns = (List) properties.get(PARQUET_BLOOM_FILTER_COLUMNS_PROPERTY) + .orElseThrow(() -> new IllegalArgumentException("The parquet_bloom_filter_columns property cannot be empty")); + validateParquetBloomFilterColumns(getColumnMetadatas(SchemaParser.fromJson(table.getTableSchemaJson()), typeManager), parquetBloomFilterColumns); + + Set existingParquetBloomFilterColumns = icebergTable.properties().keySet().stream() + .filter(key -> key.startsWith(PARQUET_BLOOM_FILTER_COLUMN_ENABLED_PREFIX)) + .map(key -> key.substring(PARQUET_BLOOM_FILTER_COLUMN_ENABLED_PREFIX.length())) + .collect(toImmutableSet()); + Set removeParquetBloomFilterColumns = Sets.difference(existingParquetBloomFilterColumns, Set.copyOf(parquetBloomFilterColumns)); + removeParquetBloomFilterColumns.forEach(column -> updateProperties.remove(PARQUET_BLOOM_FILTER_COLUMN_ENABLED_PREFIX + column)); + parquetBloomFilterColumns.forEach(column -> updateProperties.set(PARQUET_BLOOM_FILTER_COLUMN_ENABLED_PREFIX + column, "true")); + } + + if (properties.containsKey(ORC_BLOOM_FILTER_COLUMNS_PROPERTY)) { + checkFormatForProperty(getFileFormat(icebergTable).toIceberg(), FileFormat.ORC, ORC_BLOOM_FILTER_COLUMNS_PROPERTY); + //noinspection unchecked + List orcBloomFilterColumns = (List) properties.get(ORC_BLOOM_FILTER_COLUMNS_PROPERTY) + .orElseThrow(() -> new IllegalArgumentException("The orc_bloom_filter_columns property cannot be empty")); + if (orcBloomFilterColumns.isEmpty()) { + updateProperties.remove(ORC_BLOOM_FILTER_COLUMNS); + } + else { + validateOrcBloomFilterColumns(getColumnMetadatas(SchemaParser.fromJson(table.getTableSchemaJson()), typeManager), orcBloomFilterColumns); + updateProperties.set(ORC_BLOOM_FILTER_COLUMNS, Joiner.on(",").join(orcBloomFilterColumns)); + } + } + if (properties.containsKey(FILE_FORMAT_PROPERTY)) { IcebergFileFormat fileFormat = (IcebergFileFormat) properties.get(FILE_FORMAT_PROPERTY) .orElseThrow(() -> new IllegalArgumentException("The format property cannot be empty")); @@ -2811,7 +2879,7 @@ public Optional getUpdateLayout(ConnectorSession se } @Override - public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, RetryMode retryMode) + public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, Map> updateCaseColumns, RetryMode retryMode) { IcebergTableHandle table = (IcebergTableHandle) tableHandle; verifyTableVersionForUpdate(table); @@ -2867,7 +2935,7 @@ private void finishWrite(ConnectorSession session, IcebergTableHandle table, Col RowDelta rowDelta = transaction.newRowDelta(); table.getSnapshotId().map(icebergTable::snapshot).ifPresent(s -> rowDelta.validateFromSnapshot(s.snapshotId())); TupleDomain dataColumnPredicate = table.getEnforcedPredicate().filter((column, domain) -> !isMetadataColumnId(column.getId())); - TupleDomain convertibleUnenforcedPredicate = table.getUnenforcedPredicate().filter((_, domain) -> isConvertableToIcebergExpression(domain)); + TupleDomain convertibleUnenforcedPredicate = table.getUnenforcedPredicate().filter((_, domain) -> isConvertibleToIcebergExpression(domain)); TupleDomain effectivePredicate = dataColumnPredicate.intersect(convertibleUnenforcedPredicate); if (!effectivePredicate.isAll()) { rowDelta.conflictDetectionFilter(toIcebergExpression(effectivePredicate)); @@ -3112,7 +3180,7 @@ public Optional> applyFilter(C Map newUnenforced = new LinkedHashMap<>(); Map domains = predicate.getDomains().orElseThrow(() -> new VerifyException("No domains")); domains.forEach((columnHandle, domain) -> { - if (!isConvertableToIcebergExpression(domain)) { + if (!isConvertibleToIcebergExpression(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/IcebergMetadataFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadataFactory.java index 023952f6e780..6437912766b5 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadataFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadataFactory.java @@ -16,18 +16,21 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; +import io.airlift.concurrent.BoundedExecutor; import io.airlift.json.JsonCodec; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; import io.trino.spi.connector.CatalogHandle; import io.trino.spi.security.ConnectorIdentity; import io.trino.spi.type.TypeManager; import java.util.Optional; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.function.Predicate; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.util.Objects.requireNonNull; public class IcebergMetadataFactory @@ -41,7 +44,8 @@ public class IcebergMetadataFactory private final Optional metastoreFactory; private final boolean addFilesProcedureEnabled; private final Predicate allowedExtraProperties; - private final ExecutorService executor; + private final ExecutorService icebergScanExecutor; + private final Executor metadataFetchingExecutor; @Inject public IcebergMetadataFactory( @@ -52,7 +56,8 @@ public IcebergMetadataFactory( IcebergFileSystemFactory fileSystemFactory, TableStatisticsWriter tableStatisticsWriter, @RawHiveMetastoreFactory Optional metastoreFactory, - @ForIcebergScanPlanning ExecutorService executor, + @ForIcebergScanPlanning ExecutorService icebergScanExecutor, + @ForIcebergMetadata ExecutorService metadataExecutorService, IcebergConfig config) { this.typeManager = requireNonNull(typeManager, "typeManager is null"); @@ -62,7 +67,7 @@ public IcebergMetadataFactory( this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); this.tableStatisticsWriter = requireNonNull(tableStatisticsWriter, "tableStatisticsWriter is null"); this.metastoreFactory = requireNonNull(metastoreFactory, "metastoreFactory is null"); - this.executor = requireNonNull(executor, "executor is null"); + this.icebergScanExecutor = requireNonNull(icebergScanExecutor, "icebergScanExecutor is null"); this.addFilesProcedureEnabled = config.isAddFilesProcedureEnabled(); if (config.getAllowedExtraProperties().equals(ImmutableList.of("*"))) { this.allowedExtraProperties = _ -> true; @@ -70,6 +75,13 @@ public IcebergMetadataFactory( else { this.allowedExtraProperties = ImmutableSet.copyOf(requireNonNull(config.getAllowedExtraProperties(), "allowedExtraProperties is null"))::contains; } + + if (config.getMetadataParallelism() == 1) { + this.metadataFetchingExecutor = directExecutor(); + } + else { + this.metadataFetchingExecutor = new BoundedExecutor(metadataExecutorService, config.getMetadataParallelism()); + } } public IcebergMetadata create(ConnectorIdentity identity) @@ -84,6 +96,7 @@ public IcebergMetadata create(ConnectorIdentity identity) metastoreFactory, addFilesProcedureEnabled, allowedExtraProperties, - executor); + icebergScanExecutor, + metadataFetchingExecutor); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergModule.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergModule.java index 0992e51d2375..b0c653ab55a6 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergModule.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergModule.java @@ -22,6 +22,8 @@ import com.google.inject.Singleton; import com.google.inject.multibindings.Multibinder; import io.trino.filesystem.cache.CacheKeyProvider; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorPageSinkProvider; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorPageSourceProviderFactory; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorSplitManager; @@ -30,8 +32,6 @@ import io.trino.plugin.base.metrics.FileFormatDataSourceStats; import io.trino.plugin.base.session.SessionPropertiesProvider; import io.trino.plugin.hive.SortingFileWriterConfig; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; import io.trino.plugin.hive.metastore.thrift.TranslateHiveViews; import io.trino.plugin.hive.orc.OrcReaderConfig; import io.trino.plugin.hive.orc.OrcWriterConfig; @@ -42,6 +42,7 @@ import io.trino.plugin.iceberg.functions.IcebergFunctionProvider; import io.trino.plugin.iceberg.functions.tablechanges.TableChangesFunctionProcessorProviderFactory; import io.trino.plugin.iceberg.functions.tablechanges.TableChangesFunctionProvider; +import io.trino.plugin.iceberg.functions.tables.IcebergTablesFunctionProvider; import io.trino.plugin.iceberg.procedure.AddFilesTableFromTableProcedure; import io.trino.plugin.iceberg.procedure.AddFilesTableProcedure; import io.trino.plugin.iceberg.procedure.DropExtendedStatsTableProcedure; @@ -94,7 +95,7 @@ public void configure(Binder binder) binder.bind(ConnectorSplitManager.class).annotatedWith(ForClassLoaderSafe.class).to(IcebergSplitManager.class).in(Scopes.SINGLETON); binder.bind(ConnectorSplitManager.class).to(ClassLoaderSafeConnectorSplitManager.class).in(Scopes.SINGLETON); - newOptionalBinder(binder, Key.get(ConnectorPageSourceProviderFactory.class, ForClassLoaderSafe.class)).setDefault().to(IcebergPageSourceProviderFactory.class).in(Scopes.SINGLETON); + binder.bind(ConnectorPageSourceProviderFactory.class).annotatedWith(ForClassLoaderSafe.class).to(IcebergPageSourceProviderFactory.class).in(Scopes.SINGLETON); binder.bind(IcebergPageSourceProviderFactory.class).in(Scopes.SINGLETON); binder.bind(ConnectorPageSourceProviderFactory.class).to(ClassLoaderSafeConnectorPageSourceProviderFactory.class).in(Scopes.SINGLETON); binder.bind(ConnectorPageSinkProvider.class).annotatedWith(ForClassLoaderSafe.class).to(IcebergPageSinkProvider.class).in(Scopes.SINGLETON); @@ -135,7 +136,9 @@ public void configure(Binder binder) tableProcedures.addBinding().toProvider(AddFilesTableProcedure.class).in(Scopes.SINGLETON); tableProcedures.addBinding().toProvider(AddFilesTableFromTableProcedure.class).in(Scopes.SINGLETON); - newSetBinder(binder, ConnectorTableFunction.class).addBinding().toProvider(TableChangesFunctionProvider.class).in(Scopes.SINGLETON); + Multibinder tableFunctions = newSetBinder(binder, ConnectorTableFunction.class); + tableFunctions.addBinding().toProvider(TableChangesFunctionProvider.class).in(Scopes.SINGLETON); + tableFunctions.addBinding().toProvider(IcebergTablesFunctionProvider.class).in(Scopes.SINGLETON); binder.bind(FunctionProvider.class).to(IcebergFunctionProvider.class).in(Scopes.SINGLETON); binder.bind(TableChangesFunctionProcessorProviderFactory.class).in(Scopes.SINGLETON); 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 4b5abd936d98..1911d5d4426d 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 @@ -891,7 +891,7 @@ private static ReaderPageSourceWithRowPositions createParquetPageSource( start, length, dataSource, - parquetMetadata.getBlocks(), + parquetMetadata, ImmutableList.of(parquetTupleDomain), ImmutableList.of(parquetPredicate), descriptorsByPath, diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergParquetFileWriter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergParquetFileWriter.java index 7f0716b66188..06cccccebe3c 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergParquetFileWriter.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergParquetFileWriter.java @@ -33,7 +33,6 @@ import java.util.Optional; import java.util.stream.Stream; -import static io.trino.parquet.reader.MetadataReader.createParquetMetadata; import static io.trino.plugin.iceberg.util.ParquetUtil.footerMetrics; import static io.trino.plugin.iceberg.util.ParquetUtil.getSplitOffsets; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; @@ -83,12 +82,12 @@ public FileMetrics getFileMetrics() { ParquetMetadata parquetMetadata; try { - parquetMetadata = createParquetMetadata(parquetFileWriter.getFileMetadata(), new ParquetDataSourceId(location.toString())); + parquetMetadata = new ParquetMetadata(parquetFileWriter.getFileMetadata(), new ParquetDataSourceId(location.toString())); + return new FileMetrics(footerMetrics(parquetMetadata, Stream.empty(), metricsConfig), Optional.of(getSplitOffsets(parquetMetadata))); } catch (IOException e) { throw new TrinoException(GENERIC_INTERNAL_ERROR, format("Error creating metadata for Parquet file %s", location), e); } - return new FileMetrics(footerMetrics(parquetMetadata, Stream.empty(), metricsConfig), Optional.of(getSplitOffsets(parquetMetadata))); } @Override diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplitManager.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplitManager.java index f23983a4a2c3..0cb4d8d849f3 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplitManager.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplitManager.java @@ -21,6 +21,7 @@ import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorSplitSource; import io.trino.plugin.iceberg.functions.tablechanges.TableChangesFunctionHandle; import io.trino.plugin.iceberg.functions.tablechanges.TableChangesSplitSource; +import io.trino.plugin.iceberg.functions.tables.IcebergTablesFunction.IcebergTables; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.ConnectorSplitManager; import io.trino.spi.connector.ConnectorSplitSource; @@ -157,6 +158,9 @@ public ConnectorSplitSource getSplits( .toSnapshot(functionHandle.endSnapshotId())); return new ClassLoaderSafeConnectorSplitSource(tableChangesSplitSource, IcebergSplitManager.class.getClassLoader()); } + if (function instanceof IcebergTables icebergTables) { + return new ClassLoaderSafeConnectorSplitSource(new FixedSplitSource(icebergTables), IcebergSplitManager.class.getClassLoader()); + } throw new IllegalStateException("Unknown table function: " + function); } 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 1d7dd3fa8222..878add85ff2d 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 @@ -90,7 +90,7 @@ import static io.airlift.slice.Slices.utf8Slice; import static io.trino.cache.CacheUtils.uncheckedCacheGet; import static io.trino.cache.SafeCaches.buildNonEvictableCache; -import static io.trino.plugin.iceberg.ExpressionConverter.isConvertableToIcebergExpression; +import static io.trino.plugin.iceberg.ExpressionConverter.isConvertibleToIcebergExpression; import static io.trino.plugin.iceberg.ExpressionConverter.toIcebergExpression; import static io.trino.plugin.iceberg.IcebergExceptions.translateMetadataException; import static io.trino.plugin.iceberg.IcebergMetadataColumn.isMetadataColumnId; @@ -256,7 +256,7 @@ private synchronized ConnectorSplitBatch getNextBatchInternal(int maxSize) if (fileScanIterable == null) { this.pushedDownDynamicFilterPredicate = dynamicFilter.getCurrentPredicate() .transformKeys(IcebergColumnHandle.class::cast) - .filter((columnHandle, domain) -> isConvertableToIcebergExpression(domain)); + .filter((columnHandle, domain) -> isConvertibleToIcebergExpression(domain)); TupleDomain effectivePredicate = TupleDomain.intersect( ImmutableList.of(dataColumnPredicate, tableHandle.getUnenforcedPredicate(), pushedDownDynamicFilterPredicate)); 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 ecd82d188260..65922abca244 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 @@ -867,7 +867,7 @@ public static Map createTableProperties(ConnectorTableMetadata t List orcBloomFilterColumns = IcebergTableProperties.getOrcBloomFilterColumns(tableMetadata.getProperties()); if (!orcBloomFilterColumns.isEmpty()) { checkFormatForProperty(fileFormat.toIceberg(), FileFormat.ORC, ORC_BLOOM_FILTER_COLUMNS_PROPERTY); - validateOrcBloomFilterColumns(tableMetadata, orcBloomFilterColumns); + validateOrcBloomFilterColumns(tableMetadata.getColumns(), orcBloomFilterColumns); propertiesBuilder.put(ORC_BLOOM_FILTER_COLUMNS, Joiner.on(",").join(orcBloomFilterColumns)); propertiesBuilder.put(ORC_BLOOM_FILTER_FPP, String.valueOf(IcebergTableProperties.getOrcBloomFilterFpp(tableMetadata.getProperties()))); } @@ -876,7 +876,7 @@ public static Map createTableProperties(ConnectorTableMetadata t List parquetBloomFilterColumns = IcebergTableProperties.getParquetBloomFilterColumns(tableMetadata.getProperties()); if (!parquetBloomFilterColumns.isEmpty()) { checkFormatForProperty(fileFormat.toIceberg(), FileFormat.PARQUET, PARQUET_BLOOM_FILTER_COLUMNS_PROPERTY); - validateParquetBloomFilterColumns(tableMetadata, parquetBloomFilterColumns); + validateParquetBloomFilterColumns(tableMetadata.getColumns(), parquetBloomFilterColumns); for (String column : parquetBloomFilterColumns) { propertiesBuilder.put(PARQUET_BLOOM_FILTER_COLUMN_ENABLED_PREFIX + column, "true"); } @@ -986,16 +986,16 @@ public static long getSnapshotIdAsOfTime(Table table, long epochMillis) .snapshotId(); } - private static void checkFormatForProperty(FileFormat actualStorageFormat, FileFormat expectedStorageFormat, String propertyName) + public static void checkFormatForProperty(FileFormat actualStorageFormat, FileFormat expectedStorageFormat, String propertyName) { if (actualStorageFormat != expectedStorageFormat) { throw new TrinoException(INVALID_TABLE_PROPERTY, format("Cannot specify %s table property for storage format: %s", propertyName, actualStorageFormat)); } } - private static void validateOrcBloomFilterColumns(ConnectorTableMetadata tableMetadata, List orcBloomFilterColumns) + public static void validateOrcBloomFilterColumns(List columns, List orcBloomFilterColumns) { - Set allColumns = tableMetadata.getColumns().stream() + Set allColumns = columns.stream() .map(ColumnMetadata::getName) .collect(toImmutableSet()); if (!allColumns.containsAll(orcBloomFilterColumns)) { @@ -1003,9 +1003,9 @@ private static void validateOrcBloomFilterColumns(ConnectorTableMetadata tableMe } } - private static void validateParquetBloomFilterColumns(ConnectorTableMetadata tableMetadata, List parquetBloomFilterColumns) + public static void validateParquetBloomFilterColumns(List columns, List parquetBloomFilterColumns) { - Map columnTypes = tableMetadata.getColumns().stream() + Map columnTypes = columns.stream() .collect(toImmutableMap(ColumnMetadata::getName, ColumnMetadata::getType)); for (String column : parquetBloomFilterColumns) { Type type = columnTypes.get(column); 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 2ba845acc379..14dcbe856e82 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 @@ -23,6 +23,8 @@ public enum TableType MANIFESTS, PARTITIONS, FILES, + ALL_ENTRIES, + ENTRIES, PROPERTIES, 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 bfe408978e7b..498a718b567a 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 @@ -63,6 +63,7 @@ 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.CatalogUtil.deleteRemovedMetadataFiles; import static org.apache.iceberg.TableMetadataParser.getFileExtension; import static org.apache.iceberg.TableProperties.METADATA_COMPRESSION; import static org.apache.iceberg.TableProperties.METADATA_COMPRESSION_DEFAULT; @@ -174,6 +175,7 @@ public void commit(@Nullable TableMetadata base, TableMetadata metadata) } else { commitToExistingTable(base, metadata); + deleteRemovedMetadataFiles(fileIo, base, metadata); } shouldRefresh = true; 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 4daa639b8ce5..55406ef7c86d 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 @@ -71,7 +71,7 @@ import static io.trino.metastore.TableInfo.ICEBERG_MATERIALIZED_VIEW_COMMENT; import static io.trino.plugin.hive.HiveMetadata.STORAGE_TABLE; import static io.trino.plugin.hive.ViewReaderUtil.PRESTO_VIEW_FLAG; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.mappedCopy; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.mappedCopy; import static io.trino.plugin.hive.util.HiveUtil.escapeTableName; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_FILESYSTEM_ERROR; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/MetastoreValidator.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/MetastoreValidator.java index 4cde7c705eec..c0a1fe3869b8 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/MetastoreValidator.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/MetastoreValidator.java @@ -14,7 +14,7 @@ package io.trino.plugin.iceberg.catalog; import com.google.inject.Inject; -import io.trino.plugin.hive.metastore.cache.SharedHiveMetastoreCache; +import io.trino.metastore.cache.SharedHiveMetastoreCache; public class MetastoreValidator { 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 005760040b1b..c7370d65d810 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 @@ -83,6 +83,8 @@ default Optional getNamespaceSeparator() List listTables(ConnectorSession session, Optional namespace); + List listIcebergTables(ConnectorSession session, Optional namespace); + default List listViews(ConnectorSession session, Optional namespace) { return listTables(session, namespace).stream() 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 eacdccf9b731..751372cbcfc6 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 @@ -16,8 +16,8 @@ import io.trino.annotation.NotThreadSafe; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.Table; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.MetastoreUtil; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.catalog.hms.AbstractMetastoreTableOperations; import io.trino.spi.TrinoException; import io.trino.spi.connector.ConnectorSession; 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 cef2b7927262..9c4039a7b393 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 @@ -19,9 +19,9 @@ import com.google.inject.multibindings.Multibinder; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.airlift.units.Duration; +import io.trino.metastore.cache.CachingHiveMetastoreConfig; import io.trino.plugin.hive.HideDeltaLakeTables; 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; import io.trino.plugin.iceberg.catalog.MetastoreValidator; @@ -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 CachingHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule()); 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/glue/GlueIcebergTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/GlueIcebergTableOperations.java index 60aef66c65b8..ec88e7f0618f 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 @@ -48,9 +48,9 @@ import static com.google.common.base.Verify.verify; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoView; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getStorageDescriptor; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableType; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getStorageDescriptor; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableType; import static io.trino.plugin.hive.util.HiveUtil.isIcebergTable; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_COMMIT_ERROR; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; 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 7b5e4cb67d1b..12782f9957ea 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 @@ -46,8 +46,8 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.metastore.SchemaAlreadyExistsException; import io.trino.metastore.TableInfo; -import io.trino.plugin.hive.SchemaAlreadyExistsException; import io.trino.plugin.hive.TrinoViewUtil; import io.trino.plugin.hive.ViewAlreadyExistsException; import io.trino.plugin.hive.ViewReaderUtil; @@ -130,11 +130,11 @@ import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoView; import static io.trino.plugin.hive.metastore.glue.v1.AwsSdkUtil.getPaginatedResults; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getColumnParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getStorageDescriptor; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableType; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableTypeNullable; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getColumnParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getStorageDescriptor; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableType; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableTypeNullable; 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; @@ -373,9 +373,26 @@ public void renameNamespace(ConnectorSession session, String source, String targ @Override public List listTables(ConnectorSession session, Optional namespace) + { + return listTables(session, namespace, _ -> true); + } + + @Override + public List listIcebergTables(ConnectorSession session, Optional namespace) + { + return listTables(session, namespace, table -> isIcebergTable(getTableParameters(table))).stream() + .map(TableInfo::tableName) + .collect(toImmutableList()); + } + + private List listTables( + ConnectorSession session, + Optional namespace, + Predicate tablePredicate) { List>> tasks = listNamespaces(session, namespace).stream() .map(glueNamespace -> (Callable>) () -> getGlueTablesWithExceptionHandling(glueNamespace) + .filter(tablePredicate) .map(table -> mapToTableInfo(glueNamespace, table)) .collect(toImmutableList())) .collect(toImmutableList()); 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 afdc4dbd211f..b0dc44310858 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 @@ -16,8 +16,8 @@ import io.trino.annotation.NotThreadSafe; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.Table; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.MetastoreUtil; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.CreateTableException; import io.trino.plugin.iceberg.UnknownTableTypeException; import io.trino.plugin.iceberg.catalog.AbstractIcebergTableOperations; 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 65d4dac8f7a0..905a1e4c834c 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 @@ -18,8 +18,8 @@ import io.trino.metastore.AcidTransactionOwner; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.Table; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.MetastoreUtil; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.thrift.ThriftMetastore; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.TableNotFoundException; 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 b254247f9828..bcad82df2c53 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 @@ -19,9 +19,9 @@ import com.google.inject.multibindings.Multibinder; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.airlift.units.Duration; +import io.trino.metastore.cache.CachingHiveMetastoreConfig; import io.trino.plugin.hive.HideDeltaLakeTables; 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; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; @@ -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 CachingHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule()); 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/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index b450063dba6f..665a050f69b3 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 @@ -16,6 +16,7 @@ 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; @@ -27,10 +28,10 @@ import io.trino.metastore.HivePrincipal; import io.trino.metastore.PrincipalPrivileges; import io.trino.metastore.TableInfo; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.HiveSchemaProperties; import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.MetastoreUtil; -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; @@ -375,6 +376,28 @@ public List listTables(ConnectorSession session, Optional nam } } + @Override + public List listIcebergTables(ConnectorSession session, Optional namespace) + { + List>> tasks = listNamespaces(session, namespace).stream() + .map(schema -> (Callable>) () -> metastore.getTableNamesWithParameters(schema, TABLE_TYPE_PROP, ImmutableSet.of( + // Get tables with parameter table_type set to "ICEBERG" or "iceberg". This is required because + // Trino uses lowercase value whereas Spark and Flink use uppercase. + ICEBERG_TABLE_TYPE_VALUE.toLowerCase(ENGLISH), + ICEBERG_TABLE_TYPE_VALUE.toUpperCase(ENGLISH))).stream() + .map(tableName -> new SchemaTableName(schema, tableName)) + .collect(toImmutableList())) + .collect(toImmutableList()); + try { + return processWithAdditionalThreads(tasks, metadataFetchingExecutor).stream() + .flatMap(Collection::stream) + .collect(toImmutableList()); + } + catch (ExecutionException e) { + throw new RuntimeException(e.getCause()); + } + } + @Override public Optional> streamRelationColumns( ConnectorSession session, 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 98476c7ac942..2c32fc663f8f 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 @@ -16,10 +16,10 @@ import com.google.inject.Inject; import io.airlift.concurrent.BoundedExecutor; import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.NodeVersion; import io.trino.plugin.hive.TrinoViewHiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.ForIcebergMetadata; import io.trino.plugin.iceberg.IcebergConfig; import io.trino.plugin.iceberg.IcebergSecurityConfig; @@ -35,7 +35,7 @@ import java.util.concurrent.ExecutorService; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; +import static io.trino.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; 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 1d8daf1effd1..0e5c401c8ba5 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 @@ -60,6 +60,7 @@ import org.apache.iceberg.view.ViewVersion; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -200,6 +201,26 @@ public List listTables(ConnectorSession session, Optional nam return ImmutableList.copyOf(tablesListBuilder.values()); } + @Override + public List listIcebergTables(ConnectorSession session, Optional namespace) + { + List namespaces = listNamespaces(session, namespace); + + // Build as a set and convert to list for removing duplicate entries due to case difference + Set tablesListBuilder = new HashSet<>(); + for (String schemaName : namespaces) { + try { + listTableIdentifiers(schemaName, () -> jdbcCatalog.listTables(Namespace.of(schemaName))).stream() + .map(tableId -> SchemaTableName.schemaTableName(schemaName, tableId.name())) + .forEach(tablesListBuilder::add); + } + catch (NoSuchNamespaceException e) { + // Namespace may have been deleted + } + } + return ImmutableList.copyOf(tablesListBuilder); + } + @Override public List listViews(ConnectorSession session, Optional namespace) { 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 240e41f4cdd1..ed7be861fd9f 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 @@ -170,6 +170,14 @@ public List listTables(ConnectorSession session, Optional nam .collect(toImmutableList()); } + @Override + public List listIcebergTables(ConnectorSession session, Optional namespace) + { + return listTables(session, namespace).stream() + .map(TableInfo::tableName) + .collect(toImmutableList()); + } + @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 40037baed704..d72f75d3f334 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 @@ -261,6 +261,21 @@ public List listTables(ConnectorSession session, Optional nam return tables.build(); } + @Override + public List listIcebergTables(ConnectorSession session, Optional namespace) + { + SessionContext sessionContext = convert(session); + List namespaces = listNamespaces(session, namespace); + + ImmutableList.Builder tables = ImmutableList.builder(); + for (Namespace restNamespace : namespaces) { + listTableIdentifiers(restNamespace, () -> restSessionCatalog.listTables(sessionContext, toRemoteNamespace(session, restNamespace))).stream() + .map(id -> SchemaTableName.schemaTableName(toSchemaName(id.namespace()), id.name())) + .forEach(tables::add); + } + return tables.build(); + } + @Override public List listViews(ConnectorSession session, Optional namespace) { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/snowflake/TrinoSnowflakeCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/snowflake/TrinoSnowflakeCatalog.java index fb901d4890b0..2f58396e6c69 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/snowflake/TrinoSnowflakeCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/snowflake/TrinoSnowflakeCatalog.java @@ -58,6 +58,7 @@ import java.util.stream.Stream; 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.plugin.iceberg.IcebergUtil.getIcebergTableWithMetadata; import static io.trino.plugin.iceberg.IcebergUtil.quotedTableName; @@ -171,6 +172,14 @@ public List listTables(ConnectorSession session, Optional nam .toList(); } + @Override + public List listIcebergTables(ConnectorSession session, Optional namespace) + { + return listTables(session, namespace).stream() + .map(TableInfo::tableName) + .collect(toImmutableList()); + } + @Override public Optional> streamRelationColumns( ConnectorSession session, 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 07326a42bcef..b23b543a3cbf 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,12 +14,20 @@ package io.trino.plugin.iceberg.functions; import com.google.inject.Inject; +import io.trino.plugin.base.classloader.ClassLoaderSafeTableFunctionProcessorProvider; import io.trino.plugin.base.classloader.ClassLoaderSafeTableFunctionProcessorProviderFactory; +import io.trino.plugin.base.classloader.ClassLoaderSafeTableFunctionSplitProcessor; import io.trino.plugin.iceberg.functions.tablechanges.TableChangesFunctionHandle; import io.trino.plugin.iceberg.functions.tablechanges.TableChangesFunctionProcessorProviderFactory; +import io.trino.plugin.iceberg.functions.tables.IcebergTablesFunction; +import io.trino.spi.classloader.ThreadContextClassLoader; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorSplit; import io.trino.spi.function.FunctionProvider; import io.trino.spi.function.table.ConnectorTableFunctionHandle; +import io.trino.spi.function.table.TableFunctionProcessorProvider; import io.trino.spi.function.table.TableFunctionProcessorProviderFactory; +import io.trino.spi.function.table.TableFunctionSplitProcessor; import static java.util.Objects.requireNonNull; @@ -40,6 +48,28 @@ public TableFunctionProcessorProviderFactory getTableFunctionProcessorProviderFa if (functionHandle instanceof TableChangesFunctionHandle) { return new ClassLoaderSafeTableFunctionProcessorProviderFactory(tableChangesFunctionProcessorProviderFactory, getClass().getClassLoader()); } + if (functionHandle instanceof IcebergTablesFunction.IcebergTables) { + ClassLoader classLoader = getClass().getClassLoader(); + return new TableFunctionProcessorProviderFactory() + { + @Override + public TableFunctionProcessorProvider createTableFunctionProcessorProvider() + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return new ClassLoaderSafeTableFunctionProcessorProvider(new TableFunctionProcessorProvider() + { + @Override + public TableFunctionSplitProcessor getSplitProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle, ConnectorSplit split) + { + return new ClassLoaderSafeTableFunctionSplitProcessor( + new IcebergTablesFunction.IcebergTablesProcessor(((IcebergTablesFunction.IcebergTables) split).tables()), + getClass().getClassLoader()); + } + }, classLoader); + } + } + }; + } throw new UnsupportedOperationException("Unsupported function: " + functionHandle); } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tables/IcebergTablesFunction.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tables/IcebergTablesFunction.java new file mode 100644 index 000000000000..345c527a6ecf --- /dev/null +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tables/IcebergTablesFunction.java @@ -0,0 +1,129 @@ +/* + * 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.functions.tables; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.airlift.slice.Slice; +import io.airlift.slice.Slices; +import io.trino.plugin.iceberg.catalog.TrinoCatalog; +import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; +import io.trino.spi.Page; +import io.trino.spi.block.VariableWidthBlockBuilder; +import io.trino.spi.connector.ConnectorAccessControl; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorSplit; +import io.trino.spi.connector.ConnectorTransactionHandle; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.function.table.AbstractConnectorTableFunction; +import io.trino.spi.function.table.Argument; +import io.trino.spi.function.table.ConnectorTableFunctionHandle; +import io.trino.spi.function.table.Descriptor; +import io.trino.spi.function.table.ScalarArgument; +import io.trino.spi.function.table.ScalarArgumentSpecification; +import io.trino.spi.function.table.TableFunctionAnalysis; +import io.trino.spi.function.table.TableFunctionProcessorState; +import io.trino.spi.function.table.TableFunctionSplitProcessor; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static io.trino.spi.function.table.ReturnTypeSpecification.GenericTable.GENERIC_TABLE; +import static io.trino.spi.function.table.TableFunctionProcessorState.Finished.FINISHED; +import static io.trino.spi.function.table.TableFunctionProcessorState.Processed.produced; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static java.util.Objects.requireNonNull; + +public class IcebergTablesFunction + extends AbstractConnectorTableFunction +{ + private static final String FUNCTION_NAME = "iceberg_tables"; + private static final String SCHEMA_NAME_VAR_NAME = "SCHEMA_NAME"; + + private final TrinoCatalogFactory trinoCatalogFactory; + + public IcebergTablesFunction(TrinoCatalogFactory trinoCatalogFactory) + { + super( + "system", + FUNCTION_NAME, + ImmutableList.of( + ScalarArgumentSpecification.builder() + .name(SCHEMA_NAME_VAR_NAME) + .type(VARCHAR) + .defaultValue(null) + .build()), + GENERIC_TABLE); + this.trinoCatalogFactory = requireNonNull(trinoCatalogFactory, "trinoCatalogFactory is null"); + } + + @Override + public TableFunctionAnalysis analyze(ConnectorSession session, ConnectorTransactionHandle transaction, Map arguments, ConnectorAccessControl accessControl) + { + ScalarArgument argument = (ScalarArgument) getOnlyElement(arguments.values()); + Optional schemaFilter = Optional.ofNullable(((Slice) argument.getValue())).map(Slice::toStringUtf8); + + TrinoCatalog catalog = trinoCatalogFactory.create(session.getIdentity()); + List tables = catalog.listIcebergTables(session, schemaFilter); + Set filtered = accessControl.filterTables(null, ImmutableSet.copyOf(tables)); + return TableFunctionAnalysis.builder() + .returnedType(new Descriptor(ImmutableList.of( + new Descriptor.Field("table_schema", Optional.of(VARCHAR)), + new Descriptor.Field("table_name", Optional.of(VARCHAR))))) + .handle(new IcebergTables(filtered)) + .build(); + } + + public record IcebergTables(Collection tables) + implements ConnectorTableFunctionHandle, ConnectorSplit + { + public IcebergTables + { + requireNonNull(tables, "tables is null"); + } + } + + public static class IcebergTablesProcessor + implements TableFunctionSplitProcessor + { + private final Collection tables; + private boolean finished; + + public IcebergTablesProcessor(Collection tables) + { + this.tables = requireNonNull(tables, "tables is null"); + } + + @Override + public TableFunctionProcessorState process() + { + if (finished) { + return FINISHED; + } + + VariableWidthBlockBuilder schema = VARCHAR.createBlockBuilder(null, tables.size()); + VariableWidthBlockBuilder tableName = VARCHAR.createBlockBuilder(null, tables.size()); + for (SchemaTableName table : tables) { + schema.writeEntry(Slices.utf8Slice(table.getSchemaName())); + tableName.writeEntry(Slices.utf8Slice(table.getTableName())); + } + finished = true; + return produced(new Page(tables.size(), schema.build(), tableName.build())); + } + } +} diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tables/IcebergTablesFunctionProvider.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tables/IcebergTablesFunctionProvider.java new file mode 100644 index 000000000000..a0d281423f04 --- /dev/null +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tables/IcebergTablesFunctionProvider.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.plugin.iceberg.functions.tables; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorTableFunction; +import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; +import io.trino.spi.function.table.ConnectorTableFunction; + +import static java.util.Objects.requireNonNull; + +public class IcebergTablesFunctionProvider + implements Provider +{ + private final TrinoCatalogFactory trinoCatalogFactory; + + @Inject + public IcebergTablesFunctionProvider(TrinoCatalogFactory trinoCatalogFactory) + { + this.trinoCatalogFactory = requireNonNull(trinoCatalogFactory, "trinoCatalogFactory is null"); + } + + @Override + public ConnectorTableFunction get() + { + return new ClassLoaderSafeConnectorTableFunction( + new IcebergTablesFunction(trinoCatalogFactory), + getClass().getClassLoader()); + } +} 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 c9718c3ceb92..a8eceeddaeed 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 @@ -23,12 +23,12 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.metastore.Column; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.Partition; import io.trino.metastore.PrincipalPrivileges; +import io.trino.metastore.RawHiveMetastoreFactory; import io.trino.metastore.Storage; import io.trino.plugin.hive.HiveStorageFormat; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; import io.trino.plugin.iceberg.IcebergConfig; import io.trino.plugin.iceberg.IcebergFileFormat; import io.trino.plugin.iceberg.IcebergSecurityConfig; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrationUtils.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrationUtils.java index f869928de41d..ab33cb3330ec 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrationUtils.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrationUtils.java @@ -23,6 +23,7 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoInputFile; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.Partition; import io.trino.metastore.Storage; import io.trino.parquet.ParquetDataSource; @@ -31,7 +32,6 @@ import io.trino.parquet.reader.MetadataReader; import io.trino.plugin.base.metrics.FileFormatDataSourceStats; import io.trino.plugin.hive.HiveStorageFormat; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.parquet.TrinoParquetDataSource; import io.trino.plugin.iceberg.catalog.TrinoCatalog; import io.trino.plugin.iceberg.fileio.ForwardingInputFile; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/ParquetUtil.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/ParquetUtil.java index 0a676ca339ca..98f50940b419 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/ParquetUtil.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/ParquetUtil.java @@ -15,6 +15,7 @@ package io.trino.plugin.iceberg.util; import com.google.common.collect.ImmutableList; +import io.trino.parquet.ParquetCorruptionException; import io.trino.parquet.metadata.BlockMetadata; import io.trino.parquet.metadata.ColumnChunkMetadata; import io.trino.parquet.metadata.ParquetMetadata; @@ -69,6 +70,7 @@ public final class ParquetUtil private ParquetUtil() {} public static Metrics footerMetrics(ParquetMetadata metadata, Stream> fieldMetrics, MetricsConfig metricsConfig) + throws ParquetCorruptionException { return footerMetrics(metadata, fieldMetrics, metricsConfig, null); } @@ -78,6 +80,7 @@ public static Metrics footerMetrics( Stream> fieldMetrics, MetricsConfig metricsConfig, NameMapping nameMapping) + throws ParquetCorruptionException { requireNonNull(fieldMetrics, "fieldMetrics should not be null"); @@ -156,9 +159,11 @@ public static Metrics footerMetrics( } public static List getSplitOffsets(ParquetMetadata metadata) + throws ParquetCorruptionException { - List splitOffsets = new ArrayList<>(metadata.getBlocks().size()); - for (BlockMetadata blockMetaData : metadata.getBlocks()) { + List blocks = metadata.getBlocks(); + List splitOffsets = new ArrayList<>(blocks.size()); + for (BlockMetadata blockMetaData : blocks) { splitOffsets.add(blockMetaData.getStartingPos()); } Collections.sort(splitOffsets); 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 c9fef25627a5..2d2c37bcdd30 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 @@ -22,6 +22,7 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.plugin.iceberg.fileio.ForwardingFileIo; import io.trino.testing.BaseConnectorSmokeTest; +import io.trino.testing.QueryRunner; import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; import org.apache.iceberg.FileFormat; @@ -37,23 +38,32 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +import java.io.IOException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; 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; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.concurrent.MoreFutures.tryGetFutureValue; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; +import static io.trino.plugin.iceberg.IcebergTestUtils.getMetadataFileAndUpdatedMillis; import static io.trino.plugin.iceberg.IcebergTestUtils.withSmallRowGroups; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.DROP_TABLE; import static io.trino.testing.TestingAccessControlManager.privilege; +import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_CREATE_TABLE; import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; @@ -61,6 +71,7 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; +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; @@ -113,7 +124,7 @@ public void testShowCreateTable() @Test public void testHiddenPathColumn() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "hidden_file_path", "(a int, b VARCHAR)", ImmutableList.of("(1, 'a')"))) { + try (TestTable table = newTrinoTable("hidden_file_path", "(a int, b VARCHAR)", ImmutableList.of("(1, 'a')"))) { String filePath = (String) computeScalar(format("SELECT file_path FROM \"%s$files\"", table.getName())); assertQuery("SELECT DISTINCT \"$path\" FROM " + table.getName(), "VALUES " + "'" + filePath + "'"); @@ -138,8 +149,7 @@ public void testDeleteRowsConcurrently() String[] expectedErrors = new String[] {"Failed to commit the transaction during write:", "Failed to replace table due to concurrent updates:", "Failed to commit during write:"}; - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_concurrent_delete", "(col0 INTEGER, col1 INTEGER, col2 INTEGER, col3 INTEGER)")) { String tableName = table.getName(); @@ -179,8 +189,7 @@ public void testDeleteRowsConcurrently() @Test public void testCreateOrReplaceTable() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_create_or_replace", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { assertThat(query("SELECT a, b FROM " + table.getName())) @@ -524,8 +533,7 @@ public void testCreateTableWithNonExistingSchemaVerifyLocation() public void testSortedNationTable() { Session withSmallRowGroups = withSmallRowGroups(getSession()); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_sorted_nation_table", "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); @@ -545,8 +553,7 @@ public void testFileSortingWithLargerTable() .setCatalogSessionProperty("iceberg", "parquet_writer_block_size", "20kB") .setCatalogSessionProperty("iceberg", "parquet_writer_batch_size", "200") .build(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_sorted_lineitem_table", "WITH (sorted_by = ARRAY['comment'], format = '" + format.name() + "') AS TABLE tpch.tiny.lineitem WITH NO DATA")) { assertUpdate( @@ -690,8 +697,7 @@ public void testDropTableWithNonExistentTableLocation() @Test public void testMetadataTables() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_metadata_tables", "(id int, part varchar) WITH (partitioning = ARRAY['part'])")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (1, 'p1')", 1); @@ -741,8 +747,7 @@ public void testTableChangesFunction() { DateTimeFormatter instantMillisFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSVV").withZone(UTC); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_table_changes_function_", "AS SELECT nationkey, name FROM tpch.tiny.nation WITH NO DATA")) { long initialSnapshot = getMostRecentSnapshotId(table.getName()); @@ -782,8 +787,7 @@ public void testTableChangesFunction() @Test public void testRowLevelDeletesWithTableChangesFunction() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_row_level_deletes_with_table_changes_function_", "AS SELECT nationkey, regionkey, name FROM tpch.tiny.nation WITH NO DATA")) { assertUpdate("INSERT INTO " + table.getName() + " SELECT nationkey, regionkey, name FROM nation", 25); @@ -803,8 +807,7 @@ public void testCreateOrReplaceWithTableChangesFunction() { DateTimeFormatter instantMillisFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSVV").withZone(UTC); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_table_changes_function_", "AS SELECT nationkey, name FROM tpch.tiny.nation WITH NO DATA")) { long initialSnapshot = getMostRecentSnapshotId(table.getName()); @@ -835,6 +838,89 @@ public void testCreateOrReplaceWithTableChangesFunction() } } + @Test + public void testIcebergTablesFunction() + throws Exception + { + String schemaName = getSession().getSchema().orElseThrow(); + String firstSchema = "first_schema_" + randomNameSuffix(); + String secondSchema = "second_schema_" + randomNameSuffix(); + String firstSchemaLocation = schemaPath().replaceAll(schemaName, firstSchema); + String secondSchemaLocation = schemaPath().replaceAll(schemaName, secondSchema); + assertQuerySucceeds("CREATE SCHEMA " + firstSchema + " WITH (location = '%s')".formatted(firstSchemaLocation)); + assertQuerySucceeds("CREATE SCHEMA " + secondSchema + " WITH (location = '%s')".formatted(secondSchemaLocation)); + QueryRunner queryRunner = getQueryRunner(); + Session firstSchemaSession = Session.builder(queryRunner.getDefaultSession()).setSchema(firstSchema).build(); + Session secondSchemaSession = Session.builder(queryRunner.getDefaultSession()).setSchema(secondSchema).build(); + + try (TestTable _ = new TestTable( + sql -> getQueryRunner().execute(firstSchemaSession, sql), + "first_schema_table1_", + "(id int)"); + TestTable _ = new TestTable( + sql -> getQueryRunner().execute(firstSchemaSession, sql), + "first_schema_table2_", + "(id int)"); + TestTable secondSchemaTable = new TestTable( + sql -> queryRunner.execute(secondSchemaSession, sql), + "second_schema_table_", + "(id int)"); + AutoCloseable _ = createAdditionalTables(firstSchema)) { + String firstSchemaTablesValues = "VALUES " + getQueryRunner() + .execute("SELECT table_schema, table_name FROM iceberg.information_schema.tables WHERE table_schema='%s'".formatted(firstSchema)) + .getMaterializedRows().stream() + .map(row -> "('%s', '%s')".formatted(row.getField(0), row.getField(1))) + .collect(joining(", ")); + String bothSchemasTablesValues = firstSchemaTablesValues + ", ('%s', '%s')".formatted(secondSchema, secondSchemaTable.getName()); + assertQuery("SELECT * FROM TABLE(iceberg.system.iceberg_tables(SCHEMA_NAME => '%s'))".formatted(firstSchema), firstSchemaTablesValues); + assertQuery("SELECT * FROM TABLE(iceberg.system.iceberg_tables(null)) WHERE table_schema = '%s'".formatted(firstSchema), firstSchemaTablesValues); + assertQuery("SELECT * FROM TABLE(iceberg.system.iceberg_tables()) WHERE table_schema in ('%s', '%s')".formatted(firstSchema, secondSchema), bothSchemasTablesValues); + assertQuery("SELECT * FROM TABLE(iceberg.system.iceberg_tables(null)) WHERE table_schema in ('%s', '%s')".formatted(firstSchema, secondSchema), bothSchemasTablesValues); + } + finally { + assertQuerySucceeds("DROP SCHEMA " + firstSchema); + assertQuerySucceeds("DROP SCHEMA " + secondSchema); + } + } + + protected AutoCloseable createAdditionalTables(String schema) + { + return () -> {}; + } + + @Test + public void testMetadataDeleteAfterCommitEnabled() + throws IOException + { + if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { + return; + } + + int metadataPreviousVersionCount = 5; + String tableName = "test_metadata_delete_after_commit_enabled" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + "(_bigint BIGINT, _varchar VARCHAR)"); + assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES extra_properties = MAP(ARRAY['write.metadata.delete-after-commit.enabled'], ARRAY['true'])"); + assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES extra_properties = MAP(ARRAY['write.metadata.previous-versions-max'], ARRAY['" + metadataPreviousVersionCount + "'])"); + String tableLocation = getTableLocation(tableName); + + Map historyMetadataFiles = getMetadataFileAndUpdatedMillis(fileSystem, tableLocation); + for (int i = 0; i < 10; i++) { + assertUpdate("INSERT INTO " + tableName + " VALUES (1, 'a')", 1); + Map metadataFiles = getMetadataFileAndUpdatedMillis(fileSystem, tableLocation); + historyMetadataFiles.putAll(metadataFiles); + assertThat(metadataFiles.size()).isLessThanOrEqualTo(1 + metadataPreviousVersionCount); + Set expectMetadataFiles = historyMetadataFiles + .entrySet() + .stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(metadataPreviousVersionCount + 1) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + assertThat(metadataFiles.keySet()).containsAll(expectMetadataFiles); + } + assertUpdate("DROP TABLE " + tableName); + } + private long getMostRecentSnapshotId(String tableName) { return (long) Iterables.getOnlyElement(getQueryRunner().execute(format("SELECT snapshot_id FROM \"%s$snapshots\" ORDER BY committed_at DESC LIMIT 1", tableName)) @@ -849,7 +935,14 @@ private ZonedDateTime getSnapshotTime(String tableName, long snapshotId) protected String getTableLocation(String tableName) { - return (String) computeScalar("SELECT DISTINCT regexp_replace(\"$path\", '/[^/]*/[^/]*$', '') FROM " + 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"); } protected abstract void dropTableFromMetastore(String tableName); 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 670200247be5..4902182aeb87 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 @@ -215,7 +215,7 @@ public void initFileSystem() @BeforeAll public void initStorageTimePrecision() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "inspect_storage_precision", "(i int)")) { + try (TestTable table = newTrinoTable("inspect_storage_precision", "(i int)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (1)", 1); assertUpdate("INSERT INTO " + table.getName() + " VALUES (2)", 1); assertUpdate("INSERT INTO " + table.getName() + " VALUES (3)", 1); @@ -243,7 +243,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) @Test public void testAddRowFieldCaseInsensitivity() { - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_add_row_field_case_insensitivity_", "AS SELECT CAST(row(row(2)) AS row(\"CHILD\" row(grandchild_1 integer))) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(CHILD row(grandchild_1 integer))"); @@ -301,7 +301,7 @@ protected void verifyConcurrentAddColumnFailurePermissible(Exception e) @Test public void testDeleteOnV1Table() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_", "WITH (format_version = 1) AS SELECT * FROM orders")) { + try (TestTable table = newTrinoTable("test_delete_", "WITH (format_version = 1) AS SELECT * FROM orders")) { assertQueryFails("DELETE FROM " + table.getName() + " WHERE custkey <= 100", "Iceberg table updates require at least format version 2"); } } @@ -311,8 +311,7 @@ public void testDeleteOnV1Table() public void testCharVarcharComparison() { // with char->varchar coercion on table creation, this is essentially varchar/varchar comparison - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_char_varchar", "(k, v) AS VALUES" + " (-1, CAST(NULL AS CHAR(3))), " + @@ -1329,7 +1328,7 @@ private void testCreatePartitionedTableWithQuotedIdentifierCasing(String columnN @Test public void testPartitionColumnNameConflict() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_conflict_partition", "(ts timestamp, ts_day int) WITH (partitioning = ARRAY['day(ts)'])")) { + try (TestTable table = newTrinoTable("test_conflict_partition", "(ts timestamp, ts_day int) WITH (partitioning = ARRAY['day(ts)'])")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (TIMESTAMP '2021-07-24 03:43:57.987654', 1)", 1); assertThat(query("SELECT * FROM " + table.getName())) @@ -1338,7 +1337,7 @@ public void testPartitionColumnNameConflict() .matches("VALUES DATE '2021-07-24'"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_conflict_partition", "(ts timestamp, ts_day int)")) { + try (TestTable table = newTrinoTable("test_conflict_partition", "(ts timestamp, ts_day int)")) { assertUpdate("ALTER TABLE " + table.getName() + " SET PROPERTIES partitioning = ARRAY['day(ts)']"); assertUpdate("INSERT INTO " + table.getName() + " VALUES (TIMESTAMP '2021-07-24 03:43:57.987654', 1)", 1); @@ -1515,8 +1514,7 @@ private void testCreateSortedTableWithSortTransform(String columnName, String so public void testSortOrderChange() { Session withSmallRowGroups = withSmallRowGroups(getSession()); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_sort_order_change", "WITH (sorted_by = ARRAY['comment']) AS SELECT * FROM nation WITH NO DATA")) { assertUpdate(withSmallRowGroups, "INSERT INTO " + table.getName() + " SELECT * FROM nation", 25); @@ -1546,8 +1544,7 @@ public void testSortingDisabled() Session withSortingDisabled = Session.builder(withSmallRowGroups(getSession())) .setCatalogSessionProperty(ICEBERG_CATALOG, "sorted_writing_enabled", "false") .build(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_sorting_disabled", "WITH (sorted_by = ARRAY['comment']) AS SELECT * FROM nation WITH NO DATA")) { assertUpdate(withSortingDisabled, "INSERT INTO " + table.getName() + " SELECT * FROM nation", 25); @@ -1562,8 +1559,7 @@ public void testSortingDisabled() public void testOptimizeWithSortOrder() { Session withSmallRowGroups = withSmallRowGroups(getSession()); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_optimize_with_sort_order", "WITH (sorted_by = ARRAY['comment']) AS SELECT * FROM nation WITH NO DATA")) { assertUpdate("INSERT INTO " + table.getName() + " SELECT * FROM nation WHERE nationkey < 10", 10); @@ -1584,8 +1580,7 @@ public void testOptimizeWithSortOrder() public void testUpdateWithSortOrder() { Session withSmallRowGroups = withSmallRowGroups(getSession()); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_sorted_update", "WITH (sorted_by = ARRAY['comment']) AS TABLE tpch.tiny.customer WITH NO DATA")) { assertUpdate( @@ -1623,8 +1618,7 @@ public void testSortingOnNestedField() public void testDroppingSortColumn() { Session withSmallRowGroups = withSmallRowGroups(getSession()); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_dropping_sort_column", "WITH (sorted_by = ARRAY['comment']) AS SELECT * FROM nation WITH NO DATA")) { assertUpdate(withSmallRowGroups, "INSERT INTO " + table.getName() + " SELECT * FROM nation", 25); @@ -1709,6 +1703,14 @@ public void testRollbackSnapshot() assertUpdate("DROP TABLE test_rollback"); } + @Test + void testRollbackToSnapshotWithNullArgument() + { + assertQueryFails("CALL system.rollback_to_snapshot(NULL, 'customer_orders', 8954597067493422955)", ".*schema cannot be null.*"); + assertQueryFails("CALL system.rollback_to_snapshot('testdb', NULL, 8954597067493422955)", ".*table cannot be null.*"); + assertQueryFails("CALL system.rollback_to_snapshot('testdb', 'customer_orders', NULL)", ".*snapshot_id cannot be null.*"); + } + @Override protected String errorMessageForInsertIntoNotNullColumn(String columnName) { @@ -1774,7 +1776,7 @@ public void testDuplicatedFieldNames() assertQueryFails("CREATE TABLE " + tableName + "(col row(a row(x int, \"X\" int)))", "Field name 'x' specified more than once"); assertQueryFails("CREATE TABLE " + tableName + " AS SELECT cast(NULL AS row(a row(x int, \"X\" int))) col", "Field name 'x' specified more than once"); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_duplicated_field_names_", "(id int)")) { + try (TestTable table = newTrinoTable("test_duplicated_field_names_", "(id int)")) { assertQueryFails("ALTER TABLE " + table.getName() + " ADD COLUMN col row(x int, \"X\" int)", ".* Field name 'x' specified more than once"); assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN col row(\"X\" int)"); @@ -4106,8 +4108,7 @@ public void testPredicatePushdown() @Test public void testPredicateOnDataColumnIsNotPushedDown() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_predicate_on_data_column_is_not_pushed_down", "(a integer)")) { assertThat(query("SELECT * FROM " + testTable.getName() + " WHERE a = 10")) @@ -5021,7 +5022,7 @@ 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() + ")")) { + try (TestTable table = newTrinoTable("test_split_pruning_non_partitioned", "(row_id int, col " + testSetup.getTrinoTypeName() + ")")) { String tableName = table.getName(); String sampleValue = testSetup.getSampleValueLiteral(); String highValue = testSetup.getHighValueLiteral(); @@ -5100,8 +5101,7 @@ public void testSplitPruningFromDataFileStatistics() if (testSetup.isUnsupportedType()) { return; } - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "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)")) { @@ -6072,7 +6072,7 @@ public void testCollectingStatisticsWithFileModifiedTimeColumnPredicate() @Test public void testDeleteWithPathColumn() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_with_path_", "(key int)")) { + try (TestTable table = newTrinoTable("test_delete_with_path_", "(key int)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (1)", 1); sleepUninterruptibly(1, MILLISECONDS); assertUpdate("INSERT INTO " + table.getName() + " VALUES (2)", 1); @@ -6091,7 +6091,7 @@ public void testFileModifiedTimeHiddenColumn() if (storageTimePrecision.toMillis(1) > 1) { storageTimePrecision.sleep(1); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_file_modified_time_", "(col) AS VALUES (1)")) { + try (TestTable table = newTrinoTable("test_file_modified_time_", "(col) AS VALUES (1)")) { // Describe output should not have the $file_modified_time hidden column assertThat(query("DESCRIBE " + table.getName())) .skippingTypesCheck() @@ -6177,7 +6177,7 @@ public void testOptimizeWithFileModifiedTimeColumn() public void testDeleteWithFileModifiedTimeColumn() throws Exception { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_with_file_modified_time_", "(key int)")) { + try (TestTable table = newTrinoTable("test_delete_with_file_modified_time_", "(key int)")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (1)", 1); storageTimePrecision.sleep(1); assertUpdate("INSERT INTO " + table.getName() + " VALUES (2)", 1); @@ -6575,7 +6575,7 @@ public void testEmptyDelete() @Test public void testEmptyFilesTruncate() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_empty_files_truncate_", "AS SELECT 1 AS id")) { + try (TestTable table = newTrinoTable("test_empty_files_truncate_", "AS SELECT 1 AS id")) { assertUpdate("TRUNCATE TABLE " + table.getName()); assertQueryReturnsEmptyResult("SELECT * FROM \"" + table.getName() + "$files\""); } @@ -6946,7 +6946,7 @@ public void testDeleteRetainsMetadataFile() @Test public void testCreateOrReplaceTableSnapshots() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { + try (TestTable table = newTrinoTable("test_create_or_replace_", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { long v1SnapshotId = getCurrentSnapshotId(table.getName()); assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + " AS SELECT BIGINT '-42' a, DOUBLE '38.5' b", 1); @@ -6961,7 +6961,7 @@ public void testCreateOrReplaceTableSnapshots() @Test public void testCreateOrReplaceTableChangeColumnNamesAndTypes() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { + try (TestTable table = newTrinoTable("test_create_or_replace_", " AS SELECT BIGINT '42' a, DOUBLE '-38.5' b")) { long v1SnapshotId = getCurrentSnapshotId(table.getName()); assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + " AS SELECT CAST(ARRAY[ROW('test')] AS ARRAY(ROW(field VARCHAR))) a, VARCHAR 'test2' b", 1); @@ -6976,7 +6976,7 @@ public void testCreateOrReplaceTableChangeColumnNamesAndTypes() @Test public void testCreateOrReplaceTableChangePartitionedTableIntoUnpartitioned() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_", " WITH (partitioning=ARRAY['a']) AS SELECT BIGINT '42' a, 'some data' b UNION ALL SELECT BIGINT '43' a, 'another data' b")) { + try (TestTable table = newTrinoTable("test_create_or_replace_", " WITH (partitioning=ARRAY['a']) AS SELECT BIGINT '42' a, 'some data' b UNION ALL SELECT BIGINT '43' a, 'another data' b")) { long v1SnapshotId = getCurrentSnapshotId(table.getName()); assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + " WITH (sorted_by=ARRAY['a']) AS SELECT BIGINT '22' a, 'new data' b", 1); @@ -6999,7 +6999,7 @@ public void testCreateOrReplaceTableChangePartitionedTableIntoUnpartitioned() @Test public void testCreateOrReplaceTableChangeUnpartitionedTableIntoPartitioned() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_", " WITH (sorted_by=ARRAY['a']) AS SELECT BIGINT '22' a, CAST('some data' AS VARCHAR) b")) { + try (TestTable table = newTrinoTable("test_create_or_replace_", " WITH (sorted_by=ARRAY['a']) AS SELECT BIGINT '22' a, CAST('some data' AS VARCHAR) b")) { long v1SnapshotId = getCurrentSnapshotId(table.getName()); assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + " WITH (partitioning=ARRAY['a']) AS SELECT BIGINT '42' a, 'some data' b UNION ALL SELECT BIGINT '43' a, 'another data' b", 2); @@ -7022,7 +7022,7 @@ public void testCreateOrReplaceTableChangeUnpartitionedTableIntoPartitioned() @Test public void testCreateOrReplaceTableWithComments() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_", " (a BIGINT COMMENT 'This is a column') COMMENT 'This is a table'")) { + try (TestTable table = newTrinoTable("test_create_or_replace_", " (a BIGINT COMMENT 'This is a column') COMMENT 'This is a table'")) { long v1SnapshotId = getCurrentSnapshotId(table.getName()); assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + " AS SELECT 1 a", 1); @@ -7049,8 +7049,7 @@ public void testCreateOrReplaceTableWithComments() @Test public void testCreateOrReplaceTableWithSameLocation() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_create_or_replace_with_same_location_", "(a integer)")) { String initialTableLocation = getTableLocation(table.getName()); @@ -7081,7 +7080,7 @@ public void testCreateOrReplaceTableWithSameLocation() @Test public void testCreateOrReplaceTableWithChangeInLocation() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_change_location_", "(a integer) ")) { + try (TestTable table = newTrinoTable("test_create_or_replace_change_location_", "(a integer) ")) { String initialTableLocation = getTableLocation(table.getName()) + randomNameSuffix(); long v1SnapshotId = getCurrentSnapshotId(table.getName()); assertQueryFails( @@ -7444,7 +7443,7 @@ protected OptionalInt maxTableRenameLength() @Test public void testSetPartitionedColumnType() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_partitioned_column_type_", "WITH (partitioning = ARRAY['part']) AS SELECT 1 AS id, CAST(123 AS integer) AS part")) { + try (TestTable table = newTrinoTable("test_set_partitioned_column_type_", "WITH (partitioning = ARRAY['part']) AS SELECT 1 AS id, CAST(123 AS integer) AS part")) { assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN part SET DATA TYPE bigint"); assertThat(query("SELECT part FROM " + table.getName())) @@ -7459,7 +7458,7 @@ public void testSetPartitionedColumnType() @Test public void testSetTransformPartitionedColumnType() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_partitioned_column_type_", "WITH (partitioning = ARRAY['bucket(part, 10)']) AS SELECT CAST(123 AS integer) AS part")) { + try (TestTable table = newTrinoTable("test_set_partitioned_column_type_", "WITH (partitioning = ARRAY['bucket(part, 10)']) AS SELECT CAST(123 AS integer) AS part")) { assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN part SET DATA TYPE bigint"); assertThat(query("SELECT * FROM " + table.getName())) @@ -7476,12 +7475,8 @@ public void testAlterTableWithUnsupportedProperties() assertUpdate("CREATE TABLE " + tableName + " (a bigint)"); - assertQueryFails("ALTER TABLE " + tableName + " SET PROPERTIES orc_bloom_filter_columns = ARRAY['a']", - "The following properties cannot be updated: orc_bloom_filter_columns"); assertQueryFails("ALTER TABLE " + tableName + " SET PROPERTIES location = '/var/data/table/', orc_bloom_filter_fpp = 0.5", "The following properties cannot be updated: location, orc_bloom_filter_fpp"); - assertQueryFails("ALTER TABLE " + tableName + " SET PROPERTIES format = 'ORC', orc_bloom_filter_columns = ARRAY['a']", - "The following properties cannot be updated: orc_bloom_filter_columns"); assertUpdate("DROP TABLE " + tableName); } @@ -7767,8 +7762,7 @@ public void testNoRetryWhenMetadataFileInvalid() @Test public void testTableChangesFunctionAfterSchemaChange() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_table_changes_function_", "AS SELECT nationkey, name FROM tpch.tiny.nation WITH NO DATA")) { long initialSnapshot = getCurrentSnapshotId(table.getName()); @@ -8144,8 +8138,8 @@ private static Session withPartitionFilterRequired(Session session) 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)")) { + try (TestTable dataTable = newTrinoTable("data_table", "(value uuid)"); + TestTable filteringTable = newTrinoTable("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); @@ -8162,8 +8156,8 @@ public void testUuidDynamicFilter() public void testDynamicFilterWithExplicitPartitionFilter() { String catalog = getSession().getCatalog().orElseThrow(); - try (TestTable salesTable = new TestTable(getQueryRunner()::execute, "sales_table", "(date date, receipt_id varchar, amount decimal(10,2)) with (partitioning=array['date'])"); - TestTable dimensionTable = new TestTable(getQueryRunner()::execute, "dimension_table", "(date date, following_holiday boolean, year int)")) { + try (TestTable salesTable = newTrinoTable("sales_table", "(date date, receipt_id varchar, amount decimal(10,2)) with (partitioning=array['date'])"); + TestTable dimensionTable = newTrinoTable("dimension_table", "(date date, following_holiday boolean, year int)")) { assertUpdate( """ INSERT INTO %s @@ -8257,8 +8251,7 @@ private void testCreateTableWithCompressionCodec(HiveCompressionCodec compressio public void testTypeCoercionOnCreateTableAsSelect() { for (TypeCoercionTestSetup setup : typeCoercionOnCreateTableAsSelectProvider()) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_coercion_show_create_table", format("AS SELECT %s a", setup.sourceValueLiteral))) { assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(setup.newColumnType); @@ -8274,8 +8267,7 @@ public void testTypeCoercionOnCreateTableAsSelect() public void testTypeCoercionOnCreateTableAsSelectWithNoData() { for (TypeCoercionTestSetup setup : typeCoercionOnCreateTableAsSelectProvider()) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_coercion_show_create_table", format("AS SELECT %s a WITH NO DATA", setup.sourceValueLiteral))) { assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(setup.newColumnType); @@ -8462,7 +8454,7 @@ public void testAddColumnWithTypeCoercion() private void testAddColumnWithTypeCoercion(String columnType, String expectedColumnType) { - try (TestTable testTable = new TestTable(getQueryRunner()::execute, "test_coercion_add_column", "(a varchar, b row(x integer))")) { + try (TestTable testTable = newTrinoTable("test_coercion_add_column", "(a varchar, b row(x integer))")) { assertUpdate("ALTER TABLE " + testTable.getName() + " ADD COLUMN b.y " + columnType); assertThat(getColumnType(testTable.getName(), "b")).isEqualTo("row(x integer, y %s)".formatted(expectedColumnType)); @@ -8587,7 +8579,7 @@ public void testIllegalExtraPropertyKey() @Test public void testSetIllegalExtraPropertyKey() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_illegal_table_properties", "(x int)")) { + try (TestTable table = newTrinoTable("test_set_illegal_table_properties", "(x int)")) { assertQueryFails( "ALTER TABLE " + table.getName() + " SET PROPERTIES extra_properties = MAP(ARRAY['sorted_by'], ARRAY['id'])", "\\QIllegal keys in extra_properties: [sorted_by]"); @@ -8603,8 +8595,7 @@ public void testSetIllegalExtraPropertyKey() @Test // regression test for https://github.com/trinodb/trino/issues/22922 void testArrayElementChange() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_array_schema_change", "(col array(row(a varchar, b varchar)))", List.of("CAST(array[row('a', 'b')] AS array(row(a varchar, b varchar)))"))) { @@ -8623,8 +8614,7 @@ void testArrayElementChange() @Test void testRowFieldChange() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_row_schema_change", "(col row(a varchar, b varchar))")) { assertUpdate("INSERT INTO " + table.getName() + " SELECT CAST(row('a', 'b') AS row(a varchar, b varchar))", 1); 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 4fdeb620b748..e18067d047be 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 @@ -13,11 +13,15 @@ */ package io.trino.plugin.iceberg; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.minio.messages.Event; import io.trino.Session; +import io.trino.metastore.Column; import io.trino.metastore.HiveMetastore; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.metastore.HiveType; +import io.trino.metastore.Table; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; import io.trino.testing.QueryRunner; import io.trino.testing.minio.MinioClient; @@ -28,18 +32,25 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static io.trino.metastore.PrincipalPrivileges.NO_PRIVILEGES; +import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; +import static io.trino.plugin.iceberg.IcebergTestUtils.getHiveMetastore; +import static io.trino.plugin.iceberg.catalog.AbstractIcebergTableOperations.ICEBERG_METASTORE_STORAGE_FORMAT; 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.lang.String.format; import static java.util.Locale.ENGLISH; +import static org.apache.iceberg.BaseMetastoreTableOperations.ICEBERG_TABLE_TYPE_VALUE; +import static org.apache.iceberg.BaseMetastoreTableOperations.TABLE_TYPE_PROP; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; @@ -50,7 +61,7 @@ public abstract class BaseIcebergMinioConnectorSmokeTest private final String schemaName; private final String bucketName; - private HiveMinioDataLake hiveMinioDataLake; + private Hive3MinioDataLake hiveMinioDataLake; protected BaseIcebergMinioConnectorSmokeTest(FileFormat format) { @@ -63,7 +74,7 @@ protected BaseIcebergMinioConnectorSmokeTest(FileFormat format) protected QueryRunner createQueryRunner() throws Exception { - this.hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + this.hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); this.hiveMinioDataLake.start(); return IcebergQueryRunner.builder() @@ -71,7 +82,7 @@ protected QueryRunner createQueryRunner() ImmutableMap.builder() .put("iceberg.file-format", format.name()) .put("iceberg.catalog.type", "HIVE_METASTORE") - .put("hive.metastore.uri", hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint().toString()) + .put("hive.metastore.uri", hiveMinioDataLake.getHiveMetastoreEndpoint().toString()) .put("hive.metastore.thrift.client.read-timeout", "1m") // read timed out sometimes happens with the default timeout .put("fs.hadoop.enabled", "false") .put("fs.native-s3.enabled", "true") @@ -84,6 +95,7 @@ protected QueryRunner createQueryRunner() .put("s3.max-connections", "2") // verify no leaks .put("iceberg.register-table-procedure.enabled", "true") .put("iceberg.writer-sort-buffer-size", "1MB") + .put("iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max") .putAll(getAdditionalIcebergProperties()) .buildOrThrow()) .setSchemaInitializer( @@ -233,6 +245,25 @@ public void testPathContainsSpecialCharacter() assertUpdate("DROP TABLE " + tableName); } + @Override + protected AutoCloseable createAdditionalTables(String schema) + { + HiveMetastore metastore = getHiveMetastore(getQueryRunner()); + // simulate iceberg table created by spark with lowercase table type + Table lowerCaseTableType = io.trino.metastore.Table.builder() + .setDatabaseName(schema) + .setTableName("lowercase_type_" + randomNameSuffix()) + .setOwner(Optional.empty()) + .setDataColumns(ImmutableList.of(new Column("id", HiveType.HIVE_STRING, Optional.empty(), ImmutableMap.of()))) + .setTableType(EXTERNAL_TABLE.name()) + .withStorage(storage -> storage.setStorageFormat(ICEBERG_METASTORE_STORAGE_FORMAT)) + .setParameter("EXTERNAL", "TRUE") + .setParameter(TABLE_TYPE_PROP, ICEBERG_TABLE_TYPE_VALUE.toLowerCase(ENGLISH)) + .build(); + metastore.createTable(lowerCaseTableType, NO_PRIVILEGES); + return () -> metastore.dropTable(lowerCaseTableType.getDatabaseName(), lowerCaseTableType.getTableName(), true); + } + private String onMetastore(@Language("SQL") String sql) { return hiveMinioDataLake.getHiveHadoop().runOnMetastore(sql); @@ -258,7 +289,7 @@ protected void dropTableFromMetastore(String tableName) { HiveMetastore metastore = new BridgingHiveMetastore( testingThriftHiveMetastoreBuilder() - .metastoreClient(hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint()) + .metastoreClient(hiveMinioDataLake.getHiveMetastoreEndpoint()) .build(this::closeAfterClass)); metastore.dropTable(schemaName, tableName, false); assertThat(metastore.getTable(schemaName, tableName)).isEmpty(); @@ -269,7 +300,7 @@ protected String getMetadataLocation(String tableName) { HiveMetastore metastore = new BridgingHiveMetastore( testingThriftHiveMetastoreBuilder() - .metastoreClient(hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint()) + .metastoreClient(hiveMinioDataLake.getHiveMetastoreEndpoint()) .build(this::closeAfterClass)); return metastore .getTable(schemaName, tableName).orElseThrow() 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 7212f3a3aa1d..be499a524122 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 @@ -15,13 +15,19 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.metastore.HiveMetastore; import io.trino.spi.type.ArrayType; import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.DistributedQueryRunner; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; import io.trino.testing.QueryRunner; import io.trino.testing.sql.TestTable; +import org.apache.iceberg.BaseTable; import org.apache.iceberg.FileContent; +import org.apache.iceberg.Snapshot; +import org.apache.iceberg.Table; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -37,9 +43,12 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.trino.plugin.iceberg.IcebergFileFormat.ORC; import static io.trino.plugin.iceberg.IcebergFileFormat.PARQUET; +import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; +import static io.trino.plugin.iceberg.IcebergTestUtils.getHiveMetastore; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.testing.MaterializedResult.DEFAULT_PRECISION; import static io.trino.testing.MaterializedResult.resultBuilder; +import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -49,6 +58,8 @@ public abstract class BaseIcebergSystemTables extends AbstractTestQueryFramework { private final IcebergFileFormat format; + private HiveMetastore metastore; + private TrinoFileSystemFactory fileSystemFactory; protected BaseIcebergSystemTables(IcebergFileFormat format) { @@ -59,9 +70,12 @@ protected BaseIcebergSystemTables(IcebergFileFormat format) protected QueryRunner createQueryRunner() throws Exception { - return IcebergQueryRunner.builder() + DistributedQueryRunner queryRunner = IcebergQueryRunner.builder() .setIcebergProperties(ImmutableMap.of("iceberg.file-format", format.name())) .build(); + metastore = getHiveMetastore(queryRunner); + fileSystemFactory = getFileSystemFactory(queryRunner); + return queryRunner; } @BeforeAll @@ -379,7 +393,7 @@ public void testManifestsTable() @Test public void testFilesTable() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_files_table", "AS SELECT 1 x")) { + try (TestTable table = newTrinoTable("test_files_table", "AS SELECT 1 x")) { MaterializedResult result = computeActual("DESCRIBE " + table.getName()); assertThat(result.getMaterializedRows().stream().map(row -> (String) row.getField(0))) .doesNotContain("partition"); @@ -495,7 +509,7 @@ void testFilesTableReadableMetrics() private void testFilesTableReadableMetrics(@Language("SQL") String type, @Language("SQL") String values, @Language("JSON") String... readableMetrics) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_files_table", "(x " + type + ")")) { + try (TestTable table = newTrinoTable("test_files_table", "(x " + type + ")")) { getQueryRunner().execute("INSERT INTO " + table.getName() + " " + values); assertThat(computeActual("SELECT readable_metrics FROM \"" + table.getName() + "$files\"").getOnlyColumnAsSet()) .containsExactlyInAnyOrder(readableMetrics); @@ -505,7 +519,7 @@ private void testFilesTableReadableMetrics(@Language("SQL") String type, @Langua @Test public void testFilesSchemaEvolution() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_files_table", "WITH (partitioning = ARRAY['part']) AS SELECT 1 x, 2 part")) { + try (TestTable table = newTrinoTable("test_files_table", "WITH (partitioning = ARRAY['part']) AS SELECT 1 x, 2 part")) { assertThat(query("SELECT partition FROM \"" + table.getName() + "$files\"")) .matches("SELECT CAST(ROW(2) AS ROW(part int))"); @@ -523,8 +537,7 @@ public void testFilesSchemaEvolution() @Test public void testFilesNestedPartition() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_files_table", "WITH (partitioning = ARRAY['\"part.nested\"']) AS SELECT 1 x, CAST(ROW(2) AS ROW(nested int)) part")) { assertThat(query("SELECT partition.\"part.nested\" FROM \"" + table.getName() + "$files\"")) @@ -549,6 +562,106 @@ public void testFilesTableWithDelete() assertUpdate("DROP TABLE IF EXISTS test_schema.test_table_with_delete"); } + @Test + void testAllEntriesTable() + { + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_all_entries", "AS SELECT 1 id, DATE '2014-01-01' dt")) { + assertThat(query("DESCRIBE \"" + table.getName() + "$all_entries\"")) + .matches("DESCRIBE \"" + table.getName() + "$entries\""); + + assertThat(query("SELECT * FROM \"" + table.getName() + "$all_entries\"")) + .matches("SELECT * FROM \"" + table.getName() + "$entries\""); + + assertUpdate("DELETE FROM " + table.getName(), 1); + + assertThat(computeActual("SELECT status FROM \"" + table.getName() + "$all_entries\"").getOnlyColumnAsSet()) + .containsExactly(1, 2); + assertThat(computeActual("SELECT status FROM \"" + table.getName() + "$entries\"").getOnlyColumnAsSet()) + .containsExactly(2); + assertThat(query("SELECT * FROM \"" + table.getName() + "$all_entries\" WHERE status = 2")) + .matches("SELECT * FROM \"" + table.getName() + "$entries\""); + } + } + + @Test + void testEntriesTable() + { + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_entries", "AS SELECT 1 id, DATE '2014-01-01' dt")) { + assertQuery("SHOW COLUMNS FROM \"" + table.getName() + "$entries\"", + "VALUES ('status', 'integer', '', '')," + + "('snapshot_id', 'bigint', '', '')," + + "('sequence_number', 'bigint', '', '')," + + "('file_sequence_number', 'bigint', '', '')," + + "('data_file', 'row(content integer, file_path varchar, file_format varchar, spec_id integer, record_count bigint, file_size_in_bytes bigint, " + + "column_sizes map(integer, bigint), value_counts map(integer, bigint), null_value_counts map(integer, bigint), nan_value_counts map(integer, bigint), " + + "lower_bounds map(integer, varchar), upper_bounds map(integer, varchar), key_metadata varbinary, split_offsets array(bigint), " + + "equality_ids array(integer), sort_order_id integer)', '', '')," + + "('readable_metrics', 'json', '', '')"); + + Table icebergTable = loadTable(table.getName()); + Snapshot snapshot = icebergTable.currentSnapshot(); + long snapshotId = snapshot.snapshotId(); + long sequenceNumber = snapshot.sequenceNumber(); + + assertThat(computeScalar("SELECT status FROM \"" + table.getName() + "$entries\"")) + .isEqualTo(1); + assertThat(computeScalar("SELECT snapshot_id FROM \"" + table.getName() + "$entries\"")) + .isEqualTo(snapshotId); + assertThat(computeScalar("SELECT sequence_number FROM \"" + table.getName() + "$entries\"")) + .isEqualTo(sequenceNumber); + assertThat(computeScalar("SELECT file_sequence_number FROM \"" + table.getName() + "$entries\"")) + .isEqualTo(1L); + + MaterializedRow dataFile = (MaterializedRow) computeScalar("SELECT data_file FROM \"" + table.getName() + "$entries\""); + assertThat(dataFile.getFieldCount()).isEqualTo(16); + assertThat(dataFile.getField(0)).isEqualTo(0); // content + assertThat((String) dataFile.getField(1)).endsWith(format.toString().toLowerCase(ENGLISH)); // file_path + assertThat(dataFile.getField(2)).isEqualTo(format.toString()); // file_format + assertThat(dataFile.getField(3)).isEqualTo(0); // spec_id + assertThat(dataFile.getField(4)).isEqualTo(1L); // record_count + assertThat((long) dataFile.getField(5)).isPositive(); // file_size_in_bytes + assertThat(dataFile.getField(6)).isEqualTo(value(Map.of(1, 36L, 2, 36L), null)); // column_sizes + assertThat(dataFile.getField(7)).isEqualTo(Map.of(1, 1L, 2, 1L)); // value_counts + assertThat(dataFile.getField(8)).isEqualTo(Map.of(1, 0L, 2, 0L)); // null_value_counts + assertThat(dataFile.getField(9)).isEqualTo(value(Map.of(), null)); // nan_value_counts + assertThat(dataFile.getField(10)).isEqualTo(Map.of(1, "1", 2, "2014-01-01")); // lower_bounds + assertThat(dataFile.getField(11)).isEqualTo(Map.of(1, "1", 2, "2014-01-01")); // upper_bounds + assertThat(dataFile.getField(12)).isNull(); // key_metadata + assertThat(dataFile.getField(13)).isEqualTo(List.of(value(4L, 3L))); // split_offsets + assertThat(dataFile.getField(14)).isNull(); // equality_ids + assertThat(dataFile.getField(15)).isEqualTo(0); // sort_order_id + + assertThat(computeScalar("SELECT readable_metrics FROM \"" + table.getName() + "$entries\"")) + .isEqualTo("{" + + "\"dt\":{\"column_size\":" + value(36, null) + ",\"value_count\":1,\"null_value_count\":0,\"nan_value_count\":null,\"lower_bound\":\"2014-01-01\",\"upper_bound\":\"2014-01-01\"}," + + "\"id\":{\"column_size\":" + value(36, null) + ",\"value_count\":1,\"null_value_count\":0,\"nan_value_count\":null,\"lower_bound\":1,\"upper_bound\":1}" + + "}"); + } + } + + @Test + void testEntriesPartitionTable() + { + try (TestTable table = new TestTable( + getQueryRunner()::execute, + "test_entries_partition", + "WITH (partitioning = ARRAY['dt']) AS SELECT 1 id, DATE '2014-01-01' dt")) { + assertQuery("SHOW COLUMNS FROM \"" + table.getName() + "$entries\"", + "VALUES ('status', 'integer', '', '')," + + "('snapshot_id', 'bigint', '', '')," + + "('sequence_number', 'bigint', '', '')," + + "('file_sequence_number', 'bigint', '', '')," + + "('data_file', 'row(content integer, file_path varchar, file_format varchar, spec_id integer, partition row(dt date), record_count bigint, file_size_in_bytes bigint, " + + "column_sizes map(integer, bigint), value_counts map(integer, bigint), null_value_counts map(integer, bigint), nan_value_counts map(integer, bigint), " + + "lower_bounds map(integer, varchar), upper_bounds map(integer, varchar), key_metadata varbinary, split_offsets array(bigint), " + + "equality_ids array(integer), sort_order_id integer)', '', '')," + + "('readable_metrics', 'json', '', '')"); + + assertThat(query("SELECT data_file.partition FROM \"" + table.getName() + "$entries\"")) + .matches("SELECT CAST(ROW(DATE '2014-01-01') AS ROW(dt date))"); + } + } + private Long nanCount(long value) { // Parquet does not have nan count metrics @@ -565,4 +678,9 @@ private Object value(Object parquet, Object orc) { return format == PARQUET ? parquet : orc; } + + private BaseTable loadTable(String tableName) + { + return IcebergTestUtils.loadTable(tableName, metastore, fileSystemFactory, "hive", "tpch"); + } } 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 d059623c8294..b755bcea9859 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 @@ -152,6 +152,13 @@ public void testShowSchemas() assertThat(showCreateIcebergWithRedirectionsSchema).isEqualTo(getExpectedIcebergCreateSchema("iceberg_with_redirections")); } + @Test + public void testIcebergTablesFunction() + { + assertQuery("SELECT * FROM TABLE(iceberg.system.iceberg_tables(SCHEMA_NAME => '%s'))".formatted(tpchSchema), "VALUES ('%s', 'nation')".formatted(tpchSchema)); + assertQuery("SELECT * FROM TABLE(iceberg_with_redirections.system.iceberg_tables(SCHEMA_NAME => '%s'))".formatted(tpchSchema), "VALUES ('%s', 'nation')".formatted(tpchSchema)); + } + @Test public void testTimeTravelWithRedirection() throws InterruptedException 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 698658916e7e..ff7664af0ee0 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 @@ -21,8 +21,8 @@ import io.airlift.log.Logger; import io.airlift.log.Logging; import io.trino.plugin.exchange.filesystem.FileSystemExchangePlugin; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.containers.HiveHadoop; -import io.trino.plugin.hive.containers.HiveMinioDataLake; import io.trino.plugin.iceberg.catalog.jdbc.TestingIcebergJdbcServer; import io.trino.plugin.iceberg.catalog.rest.TestingPolarisCatalog; import io.trino.plugin.iceberg.containers.NessieContainer; @@ -319,7 +319,7 @@ public static void main(String[] args) { String bucketName = "test-bucket"; @SuppressWarnings("resource") - HiveMinioDataLake hiveMinioDataLake = new HiveMinioDataLake(bucketName); + Hive3MinioDataLake hiveMinioDataLake = new Hive3MinioDataLake(bucketName); hiveMinioDataLake.start(); @SuppressWarnings("resource") diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/IcebergTestUtils.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/IcebergTestUtils.java index 9d70f271f4a2..35f514551533 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/IcebergTestUtils.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/IcebergTestUtils.java @@ -15,11 +15,15 @@ import io.airlift.slice.Slice; import io.trino.Session; +import io.trino.filesystem.FileEntry; +import io.trino.filesystem.FileIterator; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.TrinoInputFile; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.orc.OrcDataSource; import io.trino.orc.OrcReader; import io.trino.orc.OrcReaderOptions; @@ -33,22 +37,25 @@ import io.trino.parquet.reader.MetadataReader; import io.trino.plugin.base.metrics.FileFormatDataSourceStats; import io.trino.plugin.hive.TrinoViewHiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.parquet.TrinoParquetDataSource; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.plugin.iceberg.catalog.TrinoCatalog; import io.trino.plugin.iceberg.catalog.file.FileMetastoreTableOperationsProvider; import io.trino.plugin.iceberg.catalog.hms.TrinoHiveCatalog; +import io.trino.plugin.iceberg.fileio.ForwardingInputFile; import io.trino.spi.catalog.CatalogName; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.type.TestingTypeManager; import io.trino.testing.QueryRunner; import org.apache.iceberg.BaseTable; +import org.apache.iceberg.TableMetadata; +import org.apache.iceberg.TableMetadataParser; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; @@ -57,9 +64,11 @@ import static com.google.common.collect.Iterators.getOnlyElement; import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; import static io.trino.plugin.iceberg.IcebergUtil.loadIcebergTable; +import static io.trino.plugin.iceberg.util.FileOperationUtils.FileType.METADATA_JSON; +import static io.trino.plugin.iceberg.util.FileOperationUtils.FileType.fromFilePath; import static io.trino.testing.TestingConnectorSession.SESSION; public final class IcebergTestUtils @@ -130,18 +139,20 @@ private static boolean checkOrcFileSorting(Supplier dataSourceSup public static boolean checkParquetFileSorting(TrinoInputFile inputFile, String sortColumnName) { ParquetMetadata parquetMetadata; + List blocks; try { parquetMetadata = MetadataReader.readFooter( new TrinoParquetDataSource(inputFile, new ParquetReaderOptions(), new FileFormatDataSourceStats()), Optional.empty()); + blocks = parquetMetadata.getBlocks(); } catch (IOException e) { throw new UncheckedIOException(e); } Comparable previousMax = null; - verify(parquetMetadata.getBlocks().size() > 1, "Test must produce at least two row groups"); - for (BlockMetadata blockMetaData : parquetMetadata.getBlocks()) { + verify(blocks.size() > 1, "Test must produce at least two row groups"); + for (BlockMetadata blockMetaData : blocks) { ColumnChunkMetadata columnMetadata = blockMetaData.columns().stream() .filter(column -> getOnlyElement(column.getPath().iterator()).equalsIgnoreCase(sortColumnName)) .collect(onlyElement()); @@ -190,4 +201,19 @@ public static BaseTable loadTable(String tableName, directExecutor()); return (BaseTable) loadIcebergTable(catalog, tableOperationsProvider, SESSION, new SchemaTableName(schemaName, tableName)); } + + public static Map getMetadataFileAndUpdatedMillis(TrinoFileSystem trinoFileSystem, String tableLocation) + throws IOException + { + FileIterator fileIterator = trinoFileSystem.listFiles(Location.of(tableLocation + "/metadata")); + Map metadataFiles = new HashMap<>(); + while (fileIterator.hasNext()) { + FileEntry entry = fileIterator.next(); + if (fromFilePath(entry.location().path()) == METADATA_JSON) { + TableMetadata tableMetadata = TableMetadataParser.read(null, new ForwardingInputFile(trinoFileSystem.newInputFile(entry.location()))); + metadataFiles.put(entry.location().path(), tableMetadata.lastUpdatedMillis()); + } + } + return metadataFiles; + } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergAbfsConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergAbfsConnectorSmokeTest.java index f627f76e2ddf..e8aed5b10a32 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergAbfsConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergAbfsConnectorSmokeTest.java @@ -92,6 +92,7 @@ protected QueryRunner createQueryRunner() .put("azure.access-key", accessKey) .put("iceberg.register-table-procedure.enabled", "true") .put("iceberg.writer-sort-buffer-size", "1MB") + .put("iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max") .buildOrThrow()) .setSchemaInitializer( SchemaInitializer.builder() diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConnectorSmokeTest.java index b4b8dbfbd57e..c698ba1434a4 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConnectorSmokeTest.java @@ -55,7 +55,8 @@ protected QueryRunner createQueryRunner() .setIcebergProperties(ImmutableMap.of( "iceberg.file-format", format.name(), "iceberg.register-table-procedure.enabled", "true", - "iceberg.writer-sort-buffer-size", "1MB")) + "iceberg.writer-sort-buffer-size", "1MB", + "iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max")) .build(); metastore = getHiveMetastore(queryRunner); return queryRunner; @@ -137,9 +138,9 @@ public void testRowConstructorColumnLimitForMergeQuery() columns += "orderkey, custkey, orderstatus, totalprice, orderpriority) "; notMatchedClause += "s.orderkey, s.custkey, s.orderstatus, s.totalprice, s.orderpriority "; matchedClause += "orderkey = s.orderkey, custkey = s.custkey, orderstatus = s.orderstatus, totalprice = t.totalprice, orderpriority = s.orderpriority "; - TestTable table = new TestTable(getQueryRunner()::execute, "test_merge_", tableDefinition); + TestTable table = newTrinoTable("test_merge_", tableDefinition); assertUpdate("INSERT INTO " + table.getName() + " " + columns + " " + selectQuery, 1); - TestTable mergeTable = new TestTable(getQueryRunner()::execute, "test_table_", tableDefinition); + TestTable mergeTable = newTrinoTable("test_table_", tableDefinition); assertUpdate("INSERT INTO " + mergeTable.getName() + " " + columns + " " + selectQuery, 1); assertUpdate( """ 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 41dd4bebbb97..8cdd3303ab48 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 @@ -101,6 +101,7 @@ protected QueryRunner createQueryRunner() .put("iceberg.file-format", format.name()) .put("iceberg.register-table-procedure.enabled", "true") .put("iceberg.writer-sort-buffer-size", "1MB") + .put("iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max") .buildOrThrow()) .setSchemaInitializer( SchemaInitializer.builder() diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergLocalConcurrentWrites.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergLocalConcurrentWrites.java index 65253a37868d..ed3cc5313aa7 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergLocalConcurrentWrites.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergLocalConcurrentWrites.java @@ -19,7 +19,7 @@ import io.trino.testing.MaterializedResult; import io.trino.testing.QueryRunner; import io.trino.testing.sql.TestTable; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.TestInstance; import java.util.List; @@ -52,7 +52,8 @@ protected QueryRunner createQueryRunner() return IcebergQueryRunner.builder().build(); } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentInserts() throws Exception { @@ -101,7 +102,8 @@ private void testConcurrentInserts(boolean partitioned) } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentInsertsSelectingFromTheSameTable() throws Exception { @@ -148,7 +150,8 @@ private void testConcurrentInsertsSelectingFromTheSameTable(boolean partitioned) } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentInsertsSelectingFromTheSameVersionedTable() throws Exception { @@ -197,7 +200,8 @@ private void testConcurrentInsertsSelectingFromTheSameVersionedTable(boolean par } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentDelete() throws Exception { @@ -238,7 +242,8 @@ void testConcurrentDelete() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentDeleteFromTheSamePartition() throws Exception { @@ -273,7 +278,8 @@ void testConcurrentDeleteFromTheSamePartition() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentTruncate() throws Exception { @@ -308,7 +314,8 @@ void testConcurrentTruncate() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentTruncateAndInserts() throws Exception { @@ -349,7 +356,8 @@ void testConcurrentTruncateAndInserts() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentNonOverlappingUpdate() throws Exception { @@ -390,16 +398,26 @@ void testConcurrentNonOverlappingUpdate() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentOverlappingUpdate() throws Exception + { + testConcurrentOverlappingUpdate(false); + testConcurrentOverlappingUpdate(true); + } + + private void testConcurrentOverlappingUpdate(boolean partitioned) + throws Exception { int threads = 3; CyclicBarrier barrier = new CyclicBarrier(threads); ExecutorService executor = newFixedThreadPool(threads); String tableName = "test_concurrent_overlapping_updates_table_" + randomNameSuffix(); - assertUpdate("CREATE TABLE " + tableName + " (a, part) WITH (partitioning = ARRAY['part']) AS VALUES (1, 10), (11, 20), (21, 30), (31, 40)", 4); + assertUpdate("CREATE TABLE " + tableName + " (a, part) " + + (partitioned ? " WITH (partitioning = ARRAY['part'])" : "") + + " AS VALUES (1, 10), (11, 20), (21, 30), (31, 40)", 4); try { List> futures = IntStream.range(0, threads) @@ -446,7 +464,8 @@ void testConcurrentOverlappingUpdate() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentNonOverlappingUpdateOnNestedPartition() throws Exception { @@ -493,7 +512,8 @@ void testConcurrentNonOverlappingUpdateOnNestedPartition() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentDeleteAndInserts() throws Exception { @@ -570,7 +590,8 @@ void testConcurrentDeleteAndInserts() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentUpdateAndInserts() throws Exception { @@ -648,7 +669,8 @@ void testConcurrentUpdateAndInserts() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentMergeAndInserts() throws Exception { @@ -728,7 +750,8 @@ void testConcurrentMergeAndInserts() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentDeleteAndDeletePushdownAndInsert() throws Exception { @@ -772,7 +795,8 @@ void testConcurrentDeleteAndDeletePushdownAndInsert() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentUpdateWithPartitionTransformation() throws Exception { @@ -782,8 +806,7 @@ void testConcurrentUpdateWithPartitionTransformation() List rows = ImmutableList.of("('A', DATE '2024-01-01')", "('B', DATE '2024-02-02')", "('C', DATE '2024-03-03')", "('D', DATE '2024-04-04')"); List partitions = ImmutableList.of("DATE '2024-01-01'", "DATE '2024-02-02'", "DATE '2024-03-03'", "DATE '2024-04-04'"); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_concurrent_update_partition_transform_table_", "(data varchar, part date) with (partitioning = array['month(part)'])")) { String tableName = table.getName(); @@ -815,7 +838,8 @@ void testConcurrentUpdateWithPartitionTransformation() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentUpdateWithNestedPartitionTransformation() throws Exception { @@ -825,8 +849,7 @@ void testConcurrentUpdateWithNestedPartitionTransformation() List rows = ImmutableList.of("('A', ROW(DATE '2024-01-01'))", "('B', ROW(DATE '2024-02-02'))", "('C', ROW(DATE '2024-03-03'))", "('D', ROW(DATE '2024-04-04'))"); List partitions = ImmutableList.of("DATE '2024-01-01'", "DATE '2024-02-02'", "DATE '2024-03-03'", "DATE '2024-04-04'"); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_concurrent_update_partition_transform_table_", "(data varchar, parent ROW (part date)) with (partitioning = array['month(\"parent.part\")'])")) { String tableName = table.getName(); @@ -858,7 +881,8 @@ void testConcurrentUpdateWithNestedPartitionTransformation() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentUpdateWithMultiplePartitionTransformation() throws Exception { @@ -874,8 +898,7 @@ void testConcurrentUpdateWithMultiplePartitionTransformation() List partitions2 = ImmutableList.of("1", "1", "1", "1"); List partitions3 = ImmutableList.of("'aaa'", "'aab'", "'aac'", "'aad'"); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_concurrent_update_multiple_partition_transform_table_", "(data varchar, part1 timestamp, part2 int, part3 varchar) with (partitioning = array['hour(part1)', 'bucket(part2, 10)', 'truncate(part3, 2)'])")) { String tableName = table.getName(); @@ -915,7 +938,8 @@ void testConcurrentUpdateWithMultiplePartitionTransformation() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentUpdateWithOverlappingPartitionTransformation() throws Exception { @@ -925,8 +949,7 @@ void testConcurrentUpdateWithOverlappingPartitionTransformation() List rows = ImmutableList.of("('A', DATE '2024-01-01')", "('B', DATE '2024-01-02')", "('C', DATE '2024-03-03')", "('D', DATE '2024-04-04')"); List partitions = ImmutableList.of("DATE '2024-01-01'", "DATE '2024-01-02'", "DATE '2024-03-03'", "DATE '2024-04-04'"); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_concurrent_update_overlapping_partition_transform_table_", "(data varchar, part date) with (partitioning = array['month(part)'])")) { String tableName = table.getName(); @@ -976,7 +999,8 @@ void testConcurrentUpdateWithOverlappingPartitionTransformation() } } - @Test + // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. + @RepeatedTest(3) void testConcurrentUpdateWithEnforcedAndUnenforcedPartitions() throws Exception { @@ -987,8 +1011,7 @@ void testConcurrentUpdateWithEnforcedAndUnenforcedPartitions() List partitions1 = ImmutableList.of("'a'", "'b'", "'c'", "'d'"); List partitions2 = ImmutableList.of("DATE '2024-01-01'", "DATE '2024-02-02'", "DATE '2024-03-03'", "DATE '2024-04-04'"); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_concurrent_update_enforced_unenforced_partition_transform_table_", // part1 is enforced and part2 is unenforced as it has transformation "(data varchar, part1 varchar, part2 date) with (partitioning = array['part1', 'month(part2)'])")) { 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 cce80e901972..43a255b7c10c 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 @@ -15,8 +15,8 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.TrinoViewHiveMetastore; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.plugin.iceberg.catalog.TrinoCatalog; import io.trino.plugin.iceberg.catalog.file.FileMetastoreTableOperationsProvider; @@ -32,7 +32,7 @@ import org.junit.jupiter.api.Test; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; import static io.trino.plugin.iceberg.IcebergTestUtils.getHiveMetastore; import static org.assertj.core.api.Assertions.assertThat; 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 d72e9454fb8e..b1d8ae8afce6 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 @@ -16,8 +16,8 @@ import com.google.common.collect.ImmutableMap; import io.trino.Session; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.TestingHivePlugin; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.spi.security.Identity; import io.trino.spi.security.SelectedRole; import io.trino.testing.AbstractTestQueryFramework; 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 23a3b809d513..c9ffe5d571bf 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 @@ -36,8 +36,10 @@ import static io.trino.plugin.hive.metastore.MetastoreMethod.GET_TABLES; import static io.trino.plugin.hive.metastore.MetastoreMethod.REPLACE_TABLE; import static io.trino.plugin.iceberg.IcebergSessionProperties.COLLECT_EXTENDED_STATISTICS_ON_WRITE; +import static io.trino.plugin.iceberg.TableType.ALL_ENTRIES; import static io.trino.plugin.iceberg.TableType.ALL_MANIFESTS; import static io.trino.plugin.iceberg.TableType.DATA; +import static io.trino.plugin.iceberg.TableType.ENTRIES; import static io.trino.plugin.iceberg.TableType.FILES; import static io.trino.plugin.iceberg.TableType.HISTORY; import static io.trino.plugin.iceberg.TableType.MANIFESTS; @@ -338,6 +340,18 @@ public void testSelectSystemTable() .addCopies(GET_TABLE, 1) .build()); + // select from $all_entries + assertMetastoreInvocations("SELECT * FROM \"test_select_snapshots$all_entries\"", + ImmutableMultiset.builder() + .addCopies(GET_TABLE, 1) + .build()); + + // select from $entries + assertMetastoreInvocations("SELECT * FROM \"test_select_snapshots$entries\"", + ImmutableMultiset.builder() + .addCopies(GET_TABLE, 1) + .build()); + // select from $properties assertMetastoreInvocations("SELECT * FROM \"test_select_snapshots$properties\"", ImmutableMultiset.builder() @@ -349,7 +363,7 @@ public void testSelectSystemTable() // This test should get updated if a new system table is added. assertThat(TableType.values()) - .containsExactly(DATA, HISTORY, METADATA_LOG_ENTRIES, SNAPSHOTS, ALL_MANIFESTS, MANIFESTS, PARTITIONS, FILES, PROPERTIES, REFS, MATERIALIZED_VIEW_STORAGE); + .containsExactly(DATA, HISTORY, METADATA_LOG_ENTRIES, SNAPSHOTS, ALL_MANIFESTS, MANIFESTS, PARTITIONS, FILES, ALL_ENTRIES, ENTRIES, PROPERTIES, REFS, MATERIALIZED_VIEW_STORAGE); } @Test 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 114728084a47..23e3e3d68b3b 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 @@ -131,7 +131,7 @@ private void testReadSingleIntegerColumnOrcFile(String orcFileResourceName, int throws Exception { checkArgument(expectedValue != 0); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_read_as_integer", "(\"_col0\") AS VALUES 0, NULL")) { + try (TestTable table = newTrinoTable("test_read_as_integer", "(\"_col0\") AS VALUES 0, NULL")) { String orcFilePath = (String) computeScalar(format("SELECT DISTINCT file_path FROM \"%s$files\"", table.getName())); byte[] orcFileData = Resources.toByteArray(getResource(orcFileResourceName)); fileSystem.newOutputFile(Location.of(orcFilePath)).createOrOverwrite(orcFileData); @@ -150,7 +150,7 @@ private void testReadSingleIntegerColumnOrcFile(String orcFileResourceName, int public void testTimeType() { // Regression test for https://github.com/trinodb/trino/issues/15603 - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_time", "(col time(6))")) { + try (TestTable table = newTrinoTable("test_time", "(col time(6))")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (TIME '13:30:00'), (TIME '14:30:00'), (NULL)", 3); assertQuery("SELECT * FROM " + table.getName(), "VALUES '13:30:00', '14:30:00', NULL"); assertQuery( 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 02efbf39d572..bd47288fec81 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 @@ -17,8 +17,8 @@ import io.trino.Session; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.TrinoViewHiveMetastore; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.plugin.iceberg.catalog.TrinoCatalog; import io.trino.plugin.iceberg.catalog.file.FileMetastoreTableOperationsProvider; @@ -47,7 +47,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.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.iceberg.DataFileRecord.toDataFileRecord; import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcWithBloomFilters.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcWithBloomFilters.java index 5e396b78ad68..91f223faf028 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcWithBloomFilters.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcWithBloomFilters.java @@ -14,12 +14,13 @@ package io.trino.plugin.iceberg; import io.trino.testing.BaseOrcWithBloomFiltersTest; -import io.trino.testing.MaterializedResult; import io.trino.testing.QueryRunner; +import io.trino.testing.sql.TestTable; import org.junit.jupiter.api.Test; -import static io.trino.testing.MaterializedResult.resultBuilder; -import static io.trino.testing.QueryAssertions.assertContains; +import java.util.Map; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +33,7 @@ protected QueryRunner createQueryRunner() throws Exception { return IcebergQueryRunner.builder() + .addIcebergProperty("iceberg.file-format", "ORC") .addIcebergProperty("hive.orc.bloom-filters.enabled", "true") .addIcebergProperty("hive.orc.default-bloom-filter-fpp", "0.001") .build(); @@ -55,14 +57,60 @@ public void testBloomFilterPropertiesArePersistedDuringCreate() "orc_bloom_filter_columns = array['a','b']," + "orc_bloom_filter_fpp = 0.1)"); - MaterializedResult actualProperties = computeActual("SELECT * FROM \"" + tableName + "$properties\""); - assertThat(actualProperties).isNotNull(); - MaterializedResult expectedProperties = resultBuilder(getSession()) - .row("write.orc.bloom.filter.columns", "a,b") - .row("write.orc.bloom.filter.fpp", "0.1").build(); - assertContains(actualProperties, expectedProperties); + assertThat(getTableProperties(tableName)) + .containsEntry("write.orc.bloom.filter.columns", "a,b") + .containsEntry("write.orc.bloom.filter.fpp", "0.1"); assertThat((String) computeScalar("SHOW CREATE TABLE " + tableName)) .contains("orc_bloom_filter_columns", "orc_bloom_filter_fpp"); } + + @Test + void testBloomFilterPropertiesArePersistedDuringSetProperties() + { + String tableName = "test_metadata_write_properties_" + randomNameSuffix(); + assertQuerySucceeds("CREATE TABLE " + tableName + "(A bigint, b bigint, c bigint)"); + + assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES orc_bloom_filter_columns = ARRAY['a','B']"); + assertThat(getTableProperties(tableName)) + .containsEntry("write.orc.bloom.filter.columns", "a,b"); + + assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES orc_bloom_filter_columns = ARRAY['a']"); + assertThat(getTableProperties(tableName)) + .containsEntry("write.orc.bloom.filter.columns", "a"); + + assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES orc_bloom_filter_columns = ARRAY[]"); + assertThat(getTableProperties(tableName)) + .doesNotContainKey("write.orc.bloom.filter.columns"); + } + + @Test + void testInvalidBloomFilterProperties() + { + String tableName = "test_invalid_bloom_filter_properties_" + randomNameSuffix(); + assertQueryFails( + "CREATE TABLE " + tableName + "(x int) WITH (orc_bloom_filter_columns = ARRAY['missing_column'])", + "\\QOrc bloom filter columns [missing_column] not present in schema"); + + assertQuerySucceeds("CREATE TABLE " + tableName + "(x array(integer))"); + assertQueryFails( + "ALTER TABLE " + tableName + " SET PROPERTIES orc_bloom_filter_columns = ARRAY['missing_column']", + "\\QOrc bloom filter columns [missing_column] not present in schema"); + } + + @Test + void testInvalidOrcBloomFilterPropertiesOnParquet() + { + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_orc_bloom_filter", "(x int) WITH (format = 'PARQUET')")) { + assertQueryFails( + "ALTER TABLE " + table.getName() + " SET PROPERTIES orc_bloom_filter_columns = ARRAY['x']", + "Cannot specify orc_bloom_filter_columns table property for storage format: PARQUET"); + } + } + + private Map getTableProperties(String tableName) + { + return computeActual("SELECT key, value FROM \"" + tableName + "$properties\"").getMaterializedRows().stream() + .collect(toImmutableMap(row -> (String) row.getField(0), row -> (String) row.getField(1))); + } } 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 99a2b2220dee..6c6a9023e59e 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 @@ -60,8 +60,7 @@ protected boolean supportsRowGroupStatistics(String typeName) @Test public void testRowGroupResetDictionary() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_row_group_reset_dictionary", "(plain_col varchar, dict_col int)")) { String tableName = table.getName(); @@ -89,8 +88,7 @@ protected Optional filterSetColumnTypesDataProvider(SetColum @Test public void testIgnoreParquetStatistics() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_ignore_parquet_statistics", "WITH (sorted_by = ARRAY['custkey']) AS TABLE tpch.tiny.customer WITH NO DATA")) { assertUpdate( @@ -122,8 +120,7 @@ public void testIgnoreParquetStatistics() @Test public void testPushdownPredicateToParquetAfterColumnRename() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_pushdown_predicate_statistics", "WITH (sorted_by = ARRAY['custkey']) AS TABLE tpch.tiny.customer WITH NO DATA")) { assertUpdate( diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetWithBloomFilters.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetWithBloomFilters.java index e29607eeaf12..a3f4648c3a7b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetWithBloomFilters.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetWithBloomFilters.java @@ -58,6 +58,49 @@ public void testBloomFilterPropertiesArePersistedDuringCreate() "format = 'parquet'," + "parquet_bloom_filter_columns = array['a','B'])"); + verifyTableProperties(tableName); + } + + @Test + void testBloomFilterPropertiesArePersistedDuringSetProperties() + { + String tableName = "test_metadata_write_properties_" + randomNameSuffix(); + assertQuerySucceeds("CREATE TABLE " + tableName + "(A bigint, b bigint, c bigint)"); + + assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES parquet_bloom_filter_columns = ARRAY['a','B']"); + verifyTableProperties(tableName); + + assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES parquet_bloom_filter_columns = ARRAY['a']"); + assertThat((String) computeScalar("SHOW CREATE TABLE " + tableName)) + .contains("parquet_bloom_filter_columns = ARRAY['a']"); + + assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES parquet_bloom_filter_columns = ARRAY[]"); + assertThat((String) computeScalar("SHOW CREATE TABLE " + tableName)) + .doesNotContain("parquet_bloom_filter_columns"); + } + + @Test + void testInvalidBloomFilterProperties() + { + String tableName = "test_invalid_bloom_filter_properties_" + randomNameSuffix(); + assertQueryFails( + "CREATE TABLE " + tableName + "(x int) WITH (parquet_bloom_filter_columns = ARRAY['missing_column'])", + "Parquet Bloom filter column missing_column not present in schema"); + assertQueryFails( + "CREATE TABLE " + tableName + "(x array(int)) WITH (parquet_bloom_filter_columns = ARRAY['x'])", + "\\QParquet Bloom filter column x has unsupported type array(integer)"); + + assertQuerySucceeds("CREATE TABLE " + tableName + "(x array(integer))"); + assertQueryFails( + "ALTER TABLE " + tableName + " SET PROPERTIES parquet_bloom_filter_columns = ARRAY['missing_column']", + "Parquet Bloom filter column missing_column not present in schema"); + assertQueryFails( + "ALTER TABLE " + tableName + " SET PROPERTIES parquet_bloom_filter_columns = ARRAY['x']", + "\\QParquet Bloom filter column x has unsupported type array(integer)"); + } + + private void verifyTableProperties(String tableName) + { MaterializedResult actualProperties = computeActual("SELECT * FROM \"" + tableName + "$properties\""); assertThat(actualProperties).isNotNull(); MaterializedResult expectedProperties = resultBuilder(getSession()) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergProjectionPushdownPlans.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergProjectionPushdownPlans.java index 25f7f6269e37..e68182fac3df 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergProjectionPushdownPlans.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergProjectionPushdownPlans.java @@ -23,7 +23,7 @@ import io.trino.metadata.TestingFunctionResolution; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.spi.connector.ColumnHandle; import io.trino.spi.function.OperatorType; import io.trino.spi.predicate.Domain; 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 62be3d6672c3..a7fb5fc85859 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 @@ -20,8 +20,8 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.cache.DefaultCachingHostAddressProvider; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.TrinoViewHiveMetastore; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.orc.OrcReaderConfig; import io.trino.plugin.hive.orc.OrcWriterConfig; import io.trino.plugin.hive.parquet.ParquetReaderConfig; @@ -84,7 +84,7 @@ import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.iceberg.IcebergSplitSource.createFileStatisticsDomain; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; import static io.trino.plugin.iceberg.IcebergTestUtils.getHiveMetastore; @@ -406,14 +406,14 @@ public void testSplitWeight() // Write position delete file FileIO fileIo = new ForwardingFileIo(fileSystemFactory.create(SESSION)); - PositionDeleteWriter writer = Parquet.writeDeletes(fileIo.newOutputFile("local:///delete_file_" + UUID.randomUUID())) + PositionDeleteWriter writer = Parquet.writeDeletes(fileIo.newOutputFile("local:///delete_file_" + UUID.randomUUID())) .createWriterFunc(GenericParquetWriter::buildWriter) .forTable(nationTable) .overwrite() .rowSchema(nationTable.schema()) .withSpec(PartitionSpec.unpartitioned()) .buildPositionWriter(); - PositionDelete positionDelete = PositionDelete.create(); + PositionDelete positionDelete = PositionDelete.create(); PositionDelete record = positionDelete.set(dataFilePath, 0, GenericRecord.create(nationTable.schema())); try (Closeable ignored = writer) { writer.write(record); 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 67aa3ab38a62..f150da7b8394 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 @@ -20,6 +20,7 @@ import io.trino.filesystem.FileEntry; import io.trino.filesystem.FileIterator; import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.metastore.Column; import io.trino.metastore.HiveMetastore; @@ -74,6 +75,7 @@ import org.junit.jupiter.api.TestInstance; import java.io.Closeable; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -96,6 +98,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; import static io.trino.plugin.iceberg.IcebergTestUtils.getHiveMetastore; +import static io.trino.plugin.iceberg.IcebergTestUtils.getMetadataFileAndUpdatedMillis; import static io.trino.plugin.iceberg.util.EqualityDeleteUtils.writeEqualityDeleteForTable; import static io.trino.plugin.iceberg.util.EqualityDeleteUtils.writeEqualityDeleteForTableWithSchema; import static io.trino.spi.type.BigintType.BIGINT; @@ -113,6 +116,8 @@ import static org.apache.iceberg.FileFormat.ORC; import static org.apache.iceberg.FileFormat.PARQUET; import static org.apache.iceberg.TableProperties.DEFAULT_NAME_MAPPING; +import static org.apache.iceberg.TableProperties.METADATA_DELETE_AFTER_COMMIT_ENABLED; +import static org.apache.iceberg.TableProperties.METADATA_PREVIOUS_VERSIONS_MAX; import static org.apache.iceberg.TableProperties.SPLIT_SIZE; import static org.apache.iceberg.mapping.NameMappingParser.toJson; import static org.assertj.core.api.Assertions.assertThat; @@ -183,7 +188,7 @@ public void testDefaultFormatVersion() @Test public void testSetPropertiesObjectStoreLayoutEnabled() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_object_store", "(x int) WITH (object_store_layout_enabled = false)")) { + try (TestTable table = newTrinoTable("test_object_store", "(x int) WITH (object_store_layout_enabled = false)")) { assertThat((String) computeScalar("SHOW CREATE TABLE " + table.getName())) .doesNotContain("object_store_layout_enabled"); assertThat(loadTable(table.getName()).properties()) @@ -200,7 +205,7 @@ public void testSetPropertiesObjectStoreLayoutEnabled() @Test public void testSetPropertiesDataLocation() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_data_location", "(x int)")) { + try (TestTable table = newTrinoTable("test_data_location", "(x int)")) { assertThat((String) computeScalar("SHOW CREATE TABLE " + table.getName())) .doesNotContain("data_location ="); assertThat(loadTable(table.getName()).properties()) @@ -410,7 +415,7 @@ public void testOptimizePopulateSplitOffsets() .setSystemProperty("task_min_writer_count", "1") .build(); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_optimize_split_offsets", "AS SELECT * FROM tpch.tiny.nation")) { + try (TestTable table = newTrinoTable("test_optimize_split_offsets", "AS SELECT * FROM tpch.tiny.nation")) { assertUpdate(session, "ALTER TABLE " + table.getName() + " EXECUTE optimize"); assertThat(computeActual("SELECT split_offsets FROM \"" + table.getName() + "$files\"")) .isEqualTo(resultBuilder(getSession(), ImmutableList.of(new ArrayType(BIGINT))) @@ -684,8 +689,7 @@ private void runOptimizeDuringWriteOperations(boolean useSmallFiles) String blackholeTable = "blackhole_table_" + randomNameSuffix(); assertUpdate("CREATE TABLE blackhole.default.%s (a INT, b INT) WITH (split_count = 1, pages_per_split = 1, rows_per_page = 1, page_processing_delay = '3s')".formatted(blackholeTable)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_optimize_during_write_operations", "(int_col INT)")) { String tableName = table.getName(); @@ -1017,7 +1021,7 @@ public void testFilesTable() @Test public void testStatsFilePruning() { - try (TestTable testTable = new TestTable(getQueryRunner()::execute, "test_stats_file_pruning_", "(a INT, b INT) WITH (partitioning = ARRAY['b'])")) { + try (TestTable testTable = newTrinoTable("test_stats_file_pruning_", "(a INT, b INT) WITH (partitioning = ARRAY['b'])")) { assertUpdate("INSERT INTO " + testTable.getName() + " VALUES (1, 10), (10, 10)", 2); assertUpdate("INSERT INTO " + testTable.getName() + " VALUES (200, 10), (300, 20)", 2); @@ -1067,7 +1071,7 @@ public void testStatsFilePruning() @Test public void testColumnStatsPruning() { - try (TestTable testTable = new TestTable(getQueryRunner()::execute, "test_column_stats_pruning_", "(a INT, b INT) WITH (partitioning = ARRAY['b'])")) { + try (TestTable testTable = newTrinoTable("test_column_stats_pruning_", "(a INT, b INT) WITH (partitioning = ARRAY['b'])")) { assertUpdate("INSERT INTO " + testTable.getName() + " VALUES (1, 10), (10, 10)", 2); assertUpdate("INSERT INTO " + testTable.getName() + " VALUES (200, 10), (300, 20)", 2); @@ -1474,8 +1478,7 @@ void testMapValueSchemaChange() private void testMapValueSchemaChange(String format, String expectedValue) { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_map_value_schema_change", "WITH (format = '" + format + "') AS SELECT CAST(map(array[1], array[row(2)]) AS map(integer, row(field integer))) col")) { Table icebergTable = loadTable(table.getName()); @@ -1516,6 +1519,39 @@ void testEnvironmentContext() } } + @Test + public void testMetadataDeleteAfterCommitEnabled() + throws IOException + { + int metadataPreviousVersionCount = 5; + String tableName = "test_metadata_delete_after_commit_enabled" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + "(_bigint BIGINT, _varchar VARCHAR)"); + BaseTable icebergTable = loadTable(tableName); + String location = icebergTable.location(); + icebergTable.updateProperties() + .set(METADATA_DELETE_AFTER_COMMIT_ENABLED, "true") + .set(METADATA_PREVIOUS_VERSIONS_MAX, String.valueOf(metadataPreviousVersionCount)) + .commit(); + + TrinoFileSystem trinoFileSystem = fileSystemFactory.create(SESSION); + Map historyMetadataFiles = getMetadataFileAndUpdatedMillis(trinoFileSystem, location); + for (int i = 0; i < 10; i++) { + assertUpdate("INSERT INTO " + tableName + " VALUES (1, 'a')", 1); + Map metadataFiles = getMetadataFileAndUpdatedMillis(trinoFileSystem, location); + historyMetadataFiles.putAll(metadataFiles); + assertThat(metadataFiles.size()).isLessThanOrEqualTo(1 + metadataPreviousVersionCount); + Set expectMetadataFiles = historyMetadataFiles + .entrySet() + .stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(metadataPreviousVersionCount + 1) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + assertThat(metadataFiles.keySet()).containsAll(expectMetadataFiles); + } + assertUpdate("DROP TABLE " + tableName); + } + private void testHighlyNestedFieldPartitioningWithTimestampTransform(String partitioning, String partitionDirectoryRegex, Set expectedPartitionDirectories) { String tableName = "test_highly_nested_field_partitioning_with_timestamp_transform_" + randomNameSuffix(); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestMetadataQueryOptimization.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestMetadataQueryOptimization.java index f38d6fafb324..446b0d5b4c70 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestMetadataQueryOptimization.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestMetadataQueryOptimization.java @@ -18,7 +18,7 @@ import io.trino.Session; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.spi.security.PrincipalType; import io.trino.sql.ir.Constant; import io.trino.sql.planner.assertions.BasePushdownPlanTest; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSharedHiveThriftMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSharedHiveThriftMetastore.java new file mode 100644 index 000000000000..6228db8ed7c6 --- /dev/null +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSharedHiveThriftMetastore.java @@ -0,0 +1,181 @@ +/* + * 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.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.Session; +import io.trino.plugin.hive.TestingHivePlugin; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; +import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.tpch.TpchPlugin; +import io.trino.testing.DistributedQueryRunner; +import io.trino.testing.QueryRunner; +import io.trino.tpch.TpchTable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; + +import java.nio.file.Path; +import java.util.Map; + +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; +import static io.trino.testing.TestingNames.randomNameSuffix; +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 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 TestSharedHiveThriftMetastore + extends BaseSharedMetastoreTest +{ + private static final String HIVE_CATALOG = "hive"; + private String bucketName; + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + bucketName = "test-iceberg-shared-metastore" + randomNameSuffix(); + HiveMinioDataLake hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); + hiveMinioDataLake.start(); + + Session icebergSession = testSessionBuilder() + .setCatalog(ICEBERG_CATALOG) + .setSchema(tpchSchema) + .build(); + Session hiveSession = testSessionBuilder() + .setCatalog(HIVE_CATALOG) + .setSchema(tpchSchema) + .build(); + + QueryRunner queryRunner = DistributedQueryRunner.builder(icebergSession).build(); + + queryRunner.installPlugin(new TpchPlugin()); + queryRunner.createCatalog("tpch", "tpch"); + + Path dataDirectory = queryRunner.getCoordinator().getBaseDataDir().resolve("iceberg_data"); + dataDirectory.toFile().deleteOnExit(); + + queryRunner.installPlugin(new IcebergPlugin()); + queryRunner.createCatalog( + ICEBERG_CATALOG, + "iceberg", + ImmutableMap.builder() + .put("iceberg.catalog.type", "HIVE_METASTORE") + .put("hive.metastore.uri", hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint().toString()) + .put("hive.metastore.thrift.client.read-timeout", "1m") // read timed out sometimes happens with the default timeout + .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") + .put("s3.streaming.part-size", "5MB") // minimize memory usage + .put("s3.max-connections", "2") // verify no leaks + .put("iceberg.register-table-procedure.enabled", "true") + .put("iceberg.writer-sort-buffer-size", "1MB") + .buildOrThrow()); + queryRunner.createCatalog( + "iceberg_with_redirections", + "iceberg", + ImmutableMap.builder() + .put("iceberg.catalog.type", "HIVE_METASTORE") + .put("hive.metastore.uri", hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint().toString()) + .put("hive.metastore.thrift.client.read-timeout", "1m") // read timed out sometimes happens with the default timeout + .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") + .put("s3.streaming.part-size", "5MB") // minimize memory usage + .put("s3.max-connections", "2") // verify no leaks + .put("iceberg.register-table-procedure.enabled", "true") + .put("iceberg.writer-sort-buffer-size", "1MB") + .put("iceberg.hive-catalog-name", "hive") + .buildOrThrow()); + + queryRunner.installPlugin(new TestingHivePlugin(dataDirectory)); + Map hiveProperties = ImmutableMap.builder() + .put("hive.metastore", "thrift") + .put("hive.metastore.uri", hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint().toString()) + .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") + .put("s3.streaming.part-size", "5MB") + .put("hive.max-partitions-per-scan", "1000") + .put("hive.max-partitions-for-eager-load", "1000") + .put("hive.security", "allow-all") + .buildOrThrow(); + queryRunner.createCatalog(HIVE_CATALOG, "hive", hiveProperties); + queryRunner.createCatalog( + "hive_with_redirections", + "hive", + ImmutableMap.builder() + .putAll(hiveProperties).put("hive.iceberg-catalog-name", "iceberg") + .buildOrThrow()); + + queryRunner.execute("CREATE SCHEMA " + tpchSchema + " WITH (location = 's3://" + bucketName + "/" + tpchSchema + "')"); + copyTpchTables(queryRunner, "tpch", TINY_SCHEMA_NAME, icebergSession, ImmutableList.of(TpchTable.NATION)); + copyTpchTables(queryRunner, "tpch", TINY_SCHEMA_NAME, hiveSession, ImmutableList.of(TpchTable.REGION)); + queryRunner.execute("CREATE SCHEMA " + testSchema + " WITH (location = 's3://" + bucketName + "/" + testSchema + "')"); + + return queryRunner; + } + + @AfterAll + public void cleanup() + { + assertQuerySucceeds("DROP TABLE IF EXISTS hive." + tpchSchema + ".region"); + assertQuerySucceeds("DROP TABLE IF EXISTS iceberg." + tpchSchema + ".nation"); + assertQuerySucceeds("DROP SCHEMA IF EXISTS hive." + tpchSchema); + assertQuerySucceeds("DROP SCHEMA IF EXISTS hive." + testSchema); + } + + @Override + protected String getExpectedHiveCreateSchema(String catalogName) + { + return """ + CREATE SCHEMA %s.%s + WITH ( + location = 's3://%s/%s' + )""" + .formatted(catalogName, tpchSchema, bucketName, tpchSchema); + } + + @Override + protected String getExpectedIcebergCreateSchema(String catalogName) + { + String expectedIcebergCreateSchema = "CREATE SCHEMA %s.%s\n" + + "AUTHORIZATION USER user\n" + + "WITH (\n" + + " location = 's3://%s/%s'\n" + + ")"; + return format(expectedIcebergCreateSchema, catalogName, tpchSchema, bucketName, tpchSchema); + } +} 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 82254efdb85a..acc0c3943fb9 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 @@ -17,13 +17,16 @@ import com.google.common.collect.ImmutableMap; import io.airlift.log.Logger; import io.trino.metastore.TableInfo; +import io.trino.metastore.TableInfo.ExtendedRelationType; import io.trino.plugin.base.util.AutoCloseableCloser; import io.trino.plugin.hive.NodeVersion; import io.trino.plugin.iceberg.CommitTaskData; +import io.trino.plugin.iceberg.IcebergFileFormat; import io.trino.plugin.iceberg.IcebergMetadata; import io.trino.plugin.iceberg.TableStatisticsWriter; import io.trino.spi.TrinoException; import io.trino.spi.connector.CatalogHandle; +import io.trino.spi.connector.ConnectorMaterializedViewDefinition; import io.trino.spi.connector.ConnectorMetadata; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.ConnectorViewDefinition; @@ -43,18 +46,25 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; import static io.airlift.json.JsonCodec.jsonCodec; import static io.trino.metastore.TableInfo.ExtendedRelationType.TABLE; +import static io.trino.metastore.TableInfo.ExtendedRelationType.TRINO_MATERIALIZED_VIEW; import static io.trino.metastore.TableInfo.ExtendedRelationType.TRINO_VIEW; import static io.trino.plugin.hive.HiveErrorCode.HIVE_DATABASE_LOCATION_ERROR; import static io.trino.plugin.iceberg.IcebergSchemaProperties.LOCATION_PROPERTY; +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.IcebergUtil.quotedTableName; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; +import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.testing.TestingNames.randomNameSuffix; @@ -122,7 +132,8 @@ public void testNonLowercaseNamespace() Optional.empty(), false, _ -> false, - newDirectExecutorService()); + newDirectExecutorService(), + directExecutor()); assertThat(icebergMetadata.schemaExists(SESSION, namespace)).as("icebergMetadata.schemaExists(namespace)") .isFalse(); assertThat(icebergMetadata.schemaExists(SESSION, schema)).as("icebergMetadata.schemaExists(schema)") @@ -362,7 +373,7 @@ public void testView() catalog.createNamespace(SESSION, namespace, defaultNamespaceProperties(namespace), new TrinoPrincipal(PrincipalType.USER, SESSION.getUser())); catalog.createView(SESSION, schemaTableName, viewDefinition, false); - assertThat(catalog.listTables(SESSION, Optional.of(namespace)).stream()).contains(new TableInfo(schemaTableName, TRINO_VIEW)); + assertThat(catalog.listTables(SESSION, Optional.of(namespace)).stream()).contains(new TableInfo(schemaTableName, getViewType())); Map views = catalog.getViews(SESSION, Optional.of(schemaTableName.getSchemaName())); assertThat(views).hasSize(1); @@ -391,6 +402,11 @@ public void testView() } } + protected ExtendedRelationType getViewType() + { + return TRINO_VIEW; + } + @Test public void testListTables() throws Exception @@ -430,15 +446,105 @@ public void testListTables() .commitTransaction(); closer.register(() -> catalog.dropTable(SESSION, table2)); + ImmutableList.Builder allTables = ImmutableList.builder() + .add(new TableInfo(table1, TABLE)) + .add(new TableInfo(table2, TABLE)); + + ImmutableList.Builder icebergTables = ImmutableList.builder() + .add(table1) + .add(table2); + SchemaTableName view = new SchemaTableName(ns2, "view"); + try { + catalog.createView( + SESSION, + view, + new ConnectorViewDefinition( + "SELECT name FROM local.tiny.nation", + Optional.empty(), + Optional.empty(), + ImmutableList.of( + new ConnectorViewDefinition.ViewColumn("name", VarcharType.createUnboundedVarcharType().getTypeId(), Optional.empty())), + Optional.empty(), + Optional.of(SESSION.getUser()), + false, + ImmutableList.of()), + false); + closer.register(() -> catalog.dropView(SESSION, view)); + allTables.add(new TableInfo(view, getViewType())); + } + catch (TrinoException e) { + assertThat(e.getErrorCode()).isEqualTo(NOT_SUPPORTED.toErrorCode()); + } + + try { + SchemaTableName materializedView = new SchemaTableName(ns2, "mv"); + createMaterializedView( + SESSION, + catalog, + materializedView, + someMaterializedView(), + ImmutableMap.of( + FILE_FORMAT_PROPERTY, IcebergFileFormat.PARQUET, + FORMAT_VERSION_PROPERTY, 1), + false, + false); + closer.register(() -> catalog.dropMaterializedView(SESSION, materializedView)); + allTables.add(new TableInfo(materializedView, TRINO_MATERIALIZED_VIEW)); + } + catch (TrinoException e) { + assertThat(e.getErrorCode()).isEqualTo(NOT_SUPPORTED.toErrorCode()); + } + + createExternalIcebergTable(catalog, ns2, closer).ifPresent(table -> { + allTables.add(new TableInfo(table, TABLE)); + icebergTables.add(table); + }); + createExternalNonIcebergTable(catalog, ns2, closer).ifPresent(table -> { + allTables.add(new TableInfo(table, TABLE)); + }); + // No namespace provided, all tables across all namespaces should be returned - assertThat(catalog.listTables(SESSION, Optional.empty())).containsAll(ImmutableList.of(new TableInfo(table1, TABLE), new TableInfo(table2, TABLE))); + assertThat(catalog.listTables(SESSION, Optional.empty())).containsAll(allTables.build()); + assertThat(catalog.listIcebergTables(SESSION, Optional.empty())).containsAll(icebergTables.build()); // Namespace is provided and exists assertThat(catalog.listTables(SESSION, Optional.of(ns1))).containsExactly(new TableInfo(table1, TABLE)); + assertThat(catalog.listIcebergTables(SESSION, Optional.of(ns1))).containsExactly(table1); // Namespace is provided and does not exist assertThat(catalog.listTables(SESSION, Optional.of("non_existing"))).isEmpty(); + assertThat(catalog.listIcebergTables(SESSION, Optional.of("non_existing"))).isEmpty(); } } + protected void createMaterializedView( + ConnectorSession session, + TrinoCatalog catalog, + SchemaTableName materializedView, + ConnectorMaterializedViewDefinition materializedViewDefinition, + Map properties, + boolean replace, + boolean ignoreExisting) + { + catalog.createMaterializedView( + session, + materializedView, + materializedViewDefinition, + properties, + replace, + ignoreExisting); + } + + protected Optional createExternalIcebergTable(TrinoCatalog catalog, String namespace, AutoCloseableCloser closer) + throws Exception + { + return Optional.empty(); + } + + protected Optional createExternalNonIcebergTable(TrinoCatalog catalog, String namespace, AutoCloseableCloser closer) + throws Exception + { + return Optional.empty(); + } + protected void assertViewDefinition(ConnectorViewDefinition actualView, ConnectorViewDefinition expectedView) { assertThat(actualView.getOriginalSql()).isEqualTo(expectedView.getOriginalSql()); @@ -452,7 +558,7 @@ protected void assertViewDefinition(ConnectorViewDefinition actualView, Connecto assertThat(actualView.isRunAsInvoker()).isEqualTo(expectedView.isRunAsInvoker()); } - private String arbitraryTableLocation(TrinoCatalog catalog, ConnectorSession session, SchemaTableName schemaTableName) + protected String arbitraryTableLocation(TrinoCatalog catalog, ConnectorSession session, SchemaTableName schemaTableName) throws Exception { try { @@ -473,4 +579,18 @@ private void assertViewColumnDefinition(ConnectorViewDefinition.ViewColumn actua assertThat(actualViewColumn.getName()).isEqualTo(expectedViewColumn.getName()); assertThat(actualViewColumn.getType()).isEqualTo(expectedViewColumn.getType()); } + + private static ConnectorMaterializedViewDefinition someMaterializedView() + { + return new ConnectorMaterializedViewDefinition( + "select 1", + Optional.empty(), + Optional.empty(), + Optional.empty(), + ImmutableList.of(new ConnectorMaterializedViewDefinition.Column("test", BIGINT.getTypeId(), Optional.empty())), + Optional.of(Duration.ZERO), + Optional.empty(), + Optional.of("owner"), + ImmutableList.of()); + } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestAbstractIcebergTableOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestAbstractIcebergTableOperations.java index 3f12c1b9e42a..ec4beb206993 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestAbstractIcebergTableOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestAbstractIcebergTableOperations.java @@ -28,7 +28,7 @@ import java.nio.file.Path; import java.util.Optional; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; import static io.trino.testing.TestingConnectorSession.SESSION; 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 a23952cc3167..9e630d451430 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 @@ -13,20 +13,29 @@ */ package io.trino.plugin.iceberg.catalog.file; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.log.Logger; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.local.LocalFileSystemFactory; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.TrinoViewHiveMetastore; -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; import io.trino.spi.catalog.CatalogName; +import io.trino.spi.connector.ConnectorMaterializedViewDefinition; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.security.PrincipalType; +import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.type.TestingTypeManager; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +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; @@ -34,12 +43,20 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +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.common.util.concurrent.MoreExecutors.directExecutor; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; +import static io.trino.plugin.iceberg.IcebergFileFormat.PARQUET; +import static io.trino.plugin.iceberg.IcebergTableProperties.FILE_FORMAT_PROPERTY; +import static io.trino.plugin.iceberg.IcebergTableProperties.FORMAT_VERSION_PROPERTY; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.testing.TestingConnectorSession.SESSION; +import static io.trino.testing.TestingNames.randomNameSuffix; +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; @@ -48,6 +65,8 @@ public class TestTrinoHiveCatalogWithFileMetastore extends BaseTrinoCatalogTest { + private static final Logger log = Logger.get(TestTrinoHiveCatalogWithFileMetastore.class); + private Path tempDir; private TrinoFileSystemFactory fileSystemFactory; private HiveMetastore metastore; @@ -87,4 +106,61 @@ protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) new IcebergConfig().isHideMaterializedViewStorageTable(), directExecutor()); } + + @Test + @Disabled + public void testDropMaterializedView() + { + testDropMaterializedView(false); + } + + @Test + public void testDropMaterializedViewWithUniqueTableLocation() + { + testDropMaterializedView(true); + } + + private void testDropMaterializedView(boolean useUniqueTableLocations) + { + TrinoCatalog catalog = createTrinoCatalog(useUniqueTableLocations); + String namespace = "test_create_mv_" + randomNameSuffix(); + String materializedViewName = "materialized_view_name"; + try { + catalog.createNamespace(SESSION, namespace, defaultNamespaceProperties(namespace), new TrinoPrincipal(PrincipalType.USER, SESSION.getUser())); + catalog.createMaterializedView( + SESSION, + new SchemaTableName(namespace, materializedViewName), + new ConnectorMaterializedViewDefinition( + "SELECT * FROM tpch.tiny.nation", + Optional.empty(), + Optional.of("catalog_name"), + Optional.of("schema_name"), + ImmutableList.of(new ConnectorMaterializedViewDefinition.Column("col1", INTEGER.getTypeId(), Optional.empty())), + Optional.empty(), + Optional.empty(), + Optional.empty(), + ImmutableList.of()), + ImmutableMap.of(FILE_FORMAT_PROPERTY, PARQUET, FORMAT_VERSION_PROPERTY, 1), + false, + false); + + catalog.dropMaterializedView(SESSION, new SchemaTableName(namespace, materializedViewName)); + } + finally { + try { + catalog.dropNamespace(SESSION, namespace); + } + catch (Exception e) { + log.warn("Failed to clean up namespace: %s", namespace); + } + } + } + + @Test + @Override + public void testListTables() + { + // the test actually works but when cleanup up the materialized view the error is thrown + assertThatThrownBy(super::testListTables).hasMessageMatching("Table 'ns2.*.mv' not found"); + } } 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 25a343fe398f..d88c0ac316be 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 @@ -18,10 +18,10 @@ import io.airlift.configuration.AbstractConfigurationAwareModule; import io.airlift.units.Duration; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; +import io.trino.metastore.RawHiveMetastoreFactory; +import io.trino.metastore.cache.CachingHiveMetastoreConfig; import io.trino.plugin.hive.metastore.CachingHiveMetastoreModule; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastoreConfig; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; import io.trino.plugin.iceberg.catalog.hms.TrinoHiveCatalogFactory; @@ -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 CachingHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule()); 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/glue/TestIcebergGlueCatalogAccessOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java index 4a063672dd85..29109e31d2ce 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 @@ -50,8 +50,10 @@ import static io.trino.plugin.hive.metastore.glue.GlueMetastoreMethod.GET_TABLES; import static io.trino.plugin.hive.metastore.glue.GlueMetastoreMethod.UPDATE_TABLE; import static io.trino.plugin.iceberg.IcebergSessionProperties.COLLECT_EXTENDED_STATISTICS_ON_WRITE; +import static io.trino.plugin.iceberg.TableType.ALL_ENTRIES; import static io.trino.plugin.iceberg.TableType.ALL_MANIFESTS; import static io.trino.plugin.iceberg.TableType.DATA; +import static io.trino.plugin.iceberg.TableType.ENTRIES; import static io.trino.plugin.iceberg.TableType.FILES; import static io.trino.plugin.iceberg.TableType.HISTORY; import static io.trino.plugin.iceberg.TableType.MANIFESTS; @@ -470,6 +472,18 @@ public void testSelectSystemTable() .add(GET_TABLE) .build()); + // select from $all_entries + assertGlueMetastoreApiInvocations("SELECT * FROM \"test_select_snapshots$all_entries\"", + ImmutableMultiset.builder() + .add(GET_TABLE) + .build()); + + // select from $entries + assertGlueMetastoreApiInvocations("SELECT * FROM \"test_select_snapshots$entries\"", + ImmutableMultiset.builder() + .add(GET_TABLE) + .build()); + // select from $properties assertGlueMetastoreApiInvocations("SELECT * FROM \"test_select_snapshots$properties\"", ImmutableMultiset.builder() @@ -487,7 +501,7 @@ public void testSelectSystemTable() // This test should get updated if a new system table is added. assertThat(TableType.values()) - .containsExactly(DATA, HISTORY, METADATA_LOG_ENTRIES, SNAPSHOTS, ALL_MANIFESTS, MANIFESTS, PARTITIONS, FILES, PROPERTIES, REFS, MATERIALIZED_VIEW_STORAGE); + .containsExactly(DATA, HISTORY, METADATA_LOG_ENTRIES, SNAPSHOTS, ALL_MANIFESTS, MANIFESTS, PARTITIONS, FILES, ALL_ENTRIES, ENTRIES, 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/TestIcebergGlueCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogConnectorSmokeTest.java index 9dec58090118..24efa6b0e4db 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 @@ -53,14 +53,14 @@ import java.util.List; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getStorageDescriptor; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableType; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getStorageDescriptor; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableType; import static io.trino.plugin.iceberg.IcebergTestUtils.checkParquetFileSorting; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.testing.TestingNames.randomNameSuffix; 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.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -82,7 +82,7 @@ public class TestIcebergGlueCatalogConnectorSmokeTest public TestIcebergGlueCatalogConnectorSmokeTest() { super(FileFormat.PARQUET); - this.bucketName = requireNonNull(System.getenv("S3_BUCKET"), "Environment S3_BUCKET was not set"); + this.bucketName = requireEnv("S3_BUCKET"); this.schemaName = "test_iceberg_smoke_" + randomNameSuffix(); glueClient = AWSGlueAsyncClientBuilder.defaultClient(); @@ -102,7 +102,8 @@ protected QueryRunner createQueryRunner() "iceberg.catalog.type", "glue", "hive.metastore.glue.default-warehouse-dir", schemaPath(), "iceberg.register-table-procedure.enabled", "true", - "iceberg.writer-sort-buffer-size", "1MB")) + "iceberg.writer-sort-buffer-size", "1MB", + "iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max")) .setSchemaInitializer( SchemaInitializer.builder() .withClonedTpchTables(REQUIRED_TPCH_TABLES) @@ -153,7 +154,7 @@ public void testRenameSchema() @Test void testGlueTableLocation() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_table_location", "AS SELECT 1 x")) { + try (TestTable table = newTrinoTable("test_table_location", "AS SELECT 1 x")) { String initialLocation = getStorageDescriptor(getGlueTable(table.getName())).orElseThrow().getLocation(); assertThat(getStorageDescriptor(getGlueTable(table.getName())).orElseThrow().getLocation()) // Using startsWith because the location has UUID suffix 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 9f3eeceab96d..299f7720af5e 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 @@ -40,7 +40,7 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.plugin.base.util.Closables.closeAllSuppress; import static io.trino.plugin.hive.metastore.glue.v1.AwsSdkUtil.getPaginatedResults; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; import static io.trino.testing.TestingNames.randomNameSuffix; import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; 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 97529860099b..81e86808e7db 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 @@ -48,7 +48,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.plugin.hive.metastore.glue.v1.AwsSdkUtil.getPaginatedResults; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableParameters; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableParameters; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; import static io.trino.plugin.iceberg.catalog.glue.GlueIcebergUtil.getTableInput; import static io.trino.testing.TestingConnectorSession.SESSION; @@ -100,7 +100,7 @@ public void cleanup() @Test public void testSkipArchive() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_skip_archive", "(col int)")) { + try (TestTable table = newTrinoTable("test_skip_archive", "(col int)")) { List tableVersionsBeforeInsert = getTableVersions(schemaName, table.getName()); assertThat(tableVersionsBeforeInsert).hasSize(1); String versionIdBeforeInsert = getOnlyElement(tableVersionsBeforeInsert).getVersionId(); @@ -118,7 +118,7 @@ public void testSkipArchive() @Test public void testNotRemoveExistingArchive() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_remove_archive", "(col int)")) { + try (TestTable table = newTrinoTable("test_remove_archive", "(col int)")) { List tableVersionsBeforeInsert = getTableVersions(schemaName, table.getName()); assertThat(tableVersionsBeforeInsert).hasSize(1); TableVersion initialVersion = getOnlyElement(tableVersionsBeforeInsert); 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 044f41f71ef1..48f2b0d1f94a 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 @@ -28,8 +28,8 @@ import java.util.stream.Collectors; import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; -import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; public class TestIcebergS3AndGlueMetastoreTest @@ -37,7 +37,7 @@ public class TestIcebergS3AndGlueMetastoreTest { public TestIcebergS3AndGlueMetastoreTest() { - super("partitioning", "location", requireNonNull(System.getenv("S3_BUCKET"), "Environment S3_BUCKET was not set")); + super("partitioning", "location", requireEnv("S3_BUCKET")); } @Override 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 ca22eb99f0b8..4e30c6a7d8c4 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 @@ -35,6 +35,7 @@ import io.trino.spi.connector.CatalogHandle; import io.trino.spi.connector.ConnectorMaterializedViewDefinition; import io.trino.spi.connector.ConnectorMetadata; +import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.MaterializedViewNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.security.PrincipalType; @@ -141,7 +142,8 @@ public void testNonLowercaseGlueDatabase() Optional.empty(), false, _ -> false, - newDirectExecutorService()); + newDirectExecutorService(), + directExecutor()); assertThat(icebergMetadata.schemaExists(SESSION, databaseName)).as("icebergMetadata.schemaExists(databaseName)") .isFalse(); assertThat(icebergMetadata.schemaExists(SESSION, trinoSchemaName)).as("icebergMetadata.schemaExists(trinoSchemaName)") @@ -256,4 +258,26 @@ public void testDefaultLocation() } } } + + @Override + protected void createMaterializedView( + ConnectorSession session, + TrinoCatalog catalog, + SchemaTableName materializedView, + ConnectorMaterializedViewDefinition materializedViewDefinition, + Map properties, + boolean replace, + boolean ignoreExisting) + { + catalog.createMaterializedView( + session, + materializedView, + materializedViewDefinition, + ImmutableMap.builder() + .putAll(properties) + .put(LOCATION_PROPERTY, "file:///tmp/a/path/" + materializedView.getTableName()) + .buildOrThrow(), + replace, + ignoreExisting); + } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DatabaseFunctionKey.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHive4CatalogWithHiveMetastore.java similarity index 60% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DatabaseFunctionKey.java rename to plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHive4CatalogWithHiveMetastore.java index 1237525abb70..7c4f0ae1e245 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DatabaseFunctionKey.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHive4CatalogWithHiveMetastore.java @@ -11,15 +11,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore; +package io.trino.plugin.iceberg.catalog.hms; -import static java.util.Objects.requireNonNull; +import io.trino.plugin.hive.containers.Hive4MinioDataLake; +import io.trino.plugin.hive.containers.HiveMinioDataLake; -public record DatabaseFunctionKey(String databaseName, String functionName) +public class TestTrinoHive4CatalogWithHiveMetastore + extends TestTrinoHiveCatalogWithHiveMetastore { - public DatabaseFunctionKey + @Override + HiveMinioDataLake hiveMinioDataLake() { - requireNonNull(databaseName, "databaseName is null"); - requireNonNull(functionName, "functionName is null"); + return new Hive4MinioDataLake(bucketName); } } 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 587f63d2accf..63d841b90083 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 @@ -30,11 +30,13 @@ import io.trino.hdfs.authentication.NoHdfsAuthentication; import io.trino.hdfs.s3.HiveS3Config; import io.trino.hdfs.s3.TrinoS3ConfigurationInitializer; +import io.trino.metastore.Table; import io.trino.metastore.TableInfo; +import io.trino.metastore.cache.CachingHiveMetastore; import io.trino.plugin.base.util.AutoCloseableCloser; import io.trino.plugin.hive.TrinoViewHiveMetastore; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.plugin.hive.containers.HiveMinioDataLake; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; import io.trino.plugin.hive.metastore.thrift.ThriftMetastore; import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreConfig; @@ -50,6 +52,10 @@ import io.trino.spi.security.PrincipalType; import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.type.TestingTypeManager; +import org.apache.iceberg.PartitionSpec; +import org.apache.iceberg.Schema; +import org.apache.iceberg.SortOrder; +import org.apache.iceberg.types.Types; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -64,9 +70,10 @@ import static com.google.common.base.Verify.verify; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static io.trino.metastore.PrincipalPrivileges.NO_PRIVILEGES; +import static io.trino.metastore.cache.CachingHiveMetastore.createPerTransactionCache; 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.createPerTransactionCache; import static io.trino.plugin.iceberg.IcebergFileFormat.PARQUET; import static io.trino.plugin.iceberg.IcebergTableProperties.FILE_FORMAT_PROPERTY; import static io.trino.plugin.iceberg.IcebergTableProperties.FORMAT_VERSION_PROPERTY; @@ -75,7 +82,10 @@ 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; +import static java.util.Locale.ENGLISH; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.apache.iceberg.BaseMetastoreTableOperations.ICEBERG_TABLE_TYPE_VALUE; +import static org.apache.iceberg.BaseMetastoreTableOperations.TABLE_TYPE_PROP; 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; @@ -87,17 +97,23 @@ public class TestTrinoHiveCatalogWithHiveMetastore { private static final Logger LOG = Logger.get(TestTrinoHiveCatalogWithHiveMetastore.class); - private AutoCloseableCloser closer = AutoCloseableCloser.create(); + private final AutoCloseableCloser closer = AutoCloseableCloser.create(); // Use MinIO for storage, since HDFS is hard to get working in a unit test private HiveMinioDataLake dataLake; private TrinoFileSystem fileSystem; - private String bucketName; + private CachingHiveMetastore metastore; + protected String bucketName; + + HiveMinioDataLake hiveMinioDataLake() + { + return new Hive3MinioDataLake(bucketName, HIVE3_IMAGE); + } @BeforeAll public void setUp() { bucketName = "test-hive-catalog-with-hms-" + randomNameSuffix(); - dataLake = closer.register(new HiveMinioDataLake(bucketName, HIVE3_IMAGE)); + dataLake = closer.register(hiveMinioDataLake()); dataLake.start(); } @@ -130,9 +146,9 @@ protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) .thriftMetastoreConfig(new ThriftMetastoreConfig() // Read timed out sometimes happens with the default timeout .setReadTimeout(new Duration(1, MINUTES))) - .metastoreClient(dataLake.getHiveHadoop().getHiveMetastoreEndpoint()) + .metastoreClient(dataLake.getHiveMetastoreEndpoint()) .build(closer::register); - CachingHiveMetastore metastore = createPerTransactionCache(new BridgingHiveMetastore(thriftMetastore), 1000); + metastore = createPerTransactionCache(new BridgingHiveMetastore(thriftMetastore), 1000); fileSystem = fileSystemFactory.create(SESSION); return new TrinoHiveCatalog( @@ -229,6 +245,48 @@ public void testCreateMaterializedView() } } + @Override + protected Optional createExternalIcebergTable(TrinoCatalog catalog, String namespace, AutoCloseableCloser closer) + throws Exception + { + // simulate iceberg table created by spark with lowercase table type + return createTableWithTableType(catalog, namespace, closer, "lowercase_type", Optional.of(ICEBERG_TABLE_TYPE_VALUE.toLowerCase(ENGLISH))); + } + + @Override + protected Optional createExternalNonIcebergTable(TrinoCatalog catalog, String namespace, AutoCloseableCloser closer) + throws Exception + { + return createTableWithTableType(catalog, namespace, closer, "non_iceberg_table", Optional.empty()); + } + + private Optional createTableWithTableType(TrinoCatalog catalog, String namespace, AutoCloseableCloser closer, String tableName, Optional tableType) + throws Exception + { + SchemaTableName lowerCaseTableTypeTable = new SchemaTableName(namespace, tableName); + catalog.newCreateTableTransaction( + SESSION, + lowerCaseTableTypeTable, + new Schema(Types.NestedField.of(1, true, "col1", Types.LongType.get())), + PartitionSpec.unpartitioned(), + SortOrder.unsorted(), + arbitraryTableLocation(catalog, SESSION, lowerCaseTableTypeTable), + ImmutableMap.of()) + .commitTransaction(); + + Table metastoreTable = metastore.getTable(namespace, tableName).get(); + + metastore.replaceTable( + namespace, + tableName, + Table.builder(metastoreTable) + .setParameter(TABLE_TYPE_PROP, tableType) + .build(), + NO_PRIVILEGES); + closer.register(() -> metastore.dropTable(namespace, tableName, true)); + return Optional.of(lowerCaseTableTypeTable); + } + @Override protected Map defaultNamespaceProperties(String namespaceName) { 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 deleted file mode 100644 index 9bd5e87b1654..000000000000 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestingIcebergHiveMetastoreCatalogModule.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.iceberg.catalog.hms; - -import com.google.inject.Binder; -import com.google.inject.Scopes; -import io.airlift.configuration.AbstractConfigurationAwareModule; -import io.airlift.units.Duration; -import io.trino.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.CachingHiveMetastoreModule; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastoreConfig; -import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreFactory; -import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; -import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; - -import java.util.concurrent.TimeUnit; - -import static io.airlift.configuration.ConfigBinder.configBinder; -import static java.util.Objects.requireNonNull; - -public class TestingIcebergHiveMetastoreCatalogModule - extends AbstractConfigurationAwareModule -{ - private final HiveMetastore hiveMetastore; - private final ThriftMetastoreFactory thriftMetastoreFactory; - - public TestingIcebergHiveMetastoreCatalogModule(HiveMetastore hiveMetastore, ThriftMetastoreFactory thriftMetastoreFactory) - { - this.hiveMetastore = requireNonNull(hiveMetastore, "hiveMetastore is null"); - this.thriftMetastoreFactory = requireNonNull(thriftMetastoreFactory, "thriftMetastoreFactory is null"); - } - - @Override - protected void setup(Binder binder) - { - 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); - binder.bind(TrinoCatalogFactory.class).to(TrinoHiveCatalogFactory.class).in(Scopes.SINGLETON); - - configBinder(binder).bindConfigDefaults(CachingHiveMetastoreConfig.class, config -> { - // ensure caching metastore wrapper isn't created, as it's not leveraged by Iceberg - config.setStatsCacheTtl(new Duration(0, TimeUnit.SECONDS)); - }); - } -} 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 c78160d77d58..e422a0f98dc0 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 @@ -108,6 +108,7 @@ protected QueryRunner createQueryRunner() .put("iceberg.jdbc-catalog.catalog-name", "tpch") .put("iceberg.register-table-procedure.enabled", "true") .put("iceberg.writer-sort-buffer-size", "1MB") + .put("iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max") .put("iceberg.jdbc-catalog.default-warehouse-dir", warehouseLocation.getAbsolutePath()) .put("iceberg.jdbc-catalog.retryable-status-codes", "57P01,57P05") .buildOrThrow()) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConnectorSmokeTest.java index 5ad8d045335c..16e49abd4ae4 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConnectorSmokeTest.java @@ -103,7 +103,8 @@ protected QueryRunner createQueryRunner() "iceberg.catalog.type", "nessie", "iceberg.nessie-catalog.uri", nessieContainer.getRestApiUri(), "iceberg.nessie-catalog.default-warehouse-dir", tempDir.toString(), - "iceberg.writer-sort-buffer-size", "1MB")) + "iceberg.writer-sort-buffer-size", "1MB", + "iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max")) .setSchemaInitializer( SchemaInitializer.builder() .withClonedTpchTables(ImmutableList.>builder() 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 index 85ddbcbaa524..d47384cc0595 100644 --- 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 @@ -74,7 +74,7 @@ protected QueryRunner createQueryRunner() @Test public void testWithValidAccessToken() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_valid_access_token", "(a INT, b VARCHAR)", ImmutableList.of("(1, 'a')"))) { + try (TestTable table = newTrinoTable("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/catalog/nessie/TestTrinoNessieCatalog.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestTrinoNessieCatalog.java index 2046ec69b861..ce0089125172 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 @@ -46,6 +46,7 @@ import java.util.Map; import java.util.Optional; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; import static io.airlift.json.JsonCodec.jsonCodec; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; @@ -191,7 +192,8 @@ public void testNonLowercaseNamespace() Optional.empty(), false, _ -> false, - newDirectExecutorService()); + newDirectExecutorService(), + directExecutor()); assertThat(icebergMetadata.schemaExists(SESSION, namespace)).as("icebergMetadata.schemaExists(namespace)") .isTrue(); assertThat(icebergMetadata.schemaExists(SESSION, schema)).as("icebergMetadata.schemaExists(schema)") diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergPolarisCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergPolarisCatalogConnectorSmokeTest.java index 9c578e94921f..0ccb0d3d4b4e 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergPolarisCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergPolarisCatalogConnectorSmokeTest.java @@ -83,6 +83,7 @@ protected QueryRunner createQueryRunner() .addIcebergProperty("iceberg.file-format", format.name()) .addIcebergProperty("iceberg.register-table-procedure.enabled", "true") .addIcebergProperty("iceberg.writer-sort-buffer-size", "1MB") + .addIcebergProperty("iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max") .addIcebergProperty("iceberg.catalog.type", "rest") .addIcebergProperty("iceberg.rest-catalog.nested-namespace-enabled", "true") .addIcebergProperty("iceberg.rest-catalog.uri", polarisCatalog.restUri() + "/api/catalog") diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergRestCatalogNestedNamespaceConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergRestCatalogNestedNamespaceConnectorSmokeTest.java index a6dea1e1a757..0aa27a61f532 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergRestCatalogNestedNamespaceConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergRestCatalogNestedNamespaceConnectorSmokeTest.java @@ -104,6 +104,7 @@ protected QueryRunner createQueryRunner() .put("iceberg.rest-catalog.uri", testServer.getBaseUrl().toString()) .put("iceberg.register-table-procedure.enabled", "true") .put("iceberg.writer-sort-buffer-size", "1MB") + .put("iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max") .buildOrThrow(); Map nestedNamespaceEnabled = ImmutableMap.builder() 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 831e945240fa..8236cd43c19c 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 @@ -102,6 +102,7 @@ protected QueryRunner createQueryRunner() .put("iceberg.rest-catalog.uri", testServer.getBaseUrl().toString()) .put("iceberg.register-table-procedure.enabled", "true") .put("iceberg.writer-sort-buffer-size", "1MB") + .put("iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max") .buildOrThrow()) .setInitialTables(REQUIRED_TPCH_TABLES) .build(); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergUnityRestCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergUnityRestCatalogConnectorSmokeTest.java index a6cdc1793d2b..f665307bc265 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergUnityRestCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergUnityRestCatalogConnectorSmokeTest.java @@ -95,7 +95,7 @@ protected String getMetadataLocation(String tableName) @Override protected String schemaPath() { - return format("%s/%s", warehouseLocation, getSession().getSchema()); + return format("%s/%s", warehouseLocation, getSession().getSchema().orElseThrow()); } @Override @@ -470,7 +470,7 @@ public void testDropTableWithMissingDataFile() public void testDropTableWithNonExistentTableLocation() { assertThatThrownBy(super::testDropTableWithNonExistentTableLocation) - .hasMessageContaining("Access Denied"); + .hasStackTraceContaining("Access Denied"); } @Test @@ -520,4 +520,20 @@ public void testTruncateTable() assertThatThrownBy(super::testTruncateTable) .hasMessageContaining("Access Denied"); } + + @Test + @Override + public void testIcebergTablesFunction() + { + assertThatThrownBy(super::testIcebergTablesFunction) + .hasStackTraceContaining("Access Denied"); + } + + @Test + @Override + public void testMetadataDeleteAfterCommitEnabled() + { + assertThatThrownBy(super::testMetadataDeleteAfterCommitEnabled) + .hasStackTraceContaining("Access Denied"); + } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergVendingRestCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergVendingRestCatalogConnectorSmokeTest.java index c2efa55daa8d..acf12607fa13 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergVendingRestCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergVendingRestCatalogConnectorSmokeTest.java @@ -120,6 +120,7 @@ protected QueryRunner createQueryRunner() .put("iceberg.rest-catalog.uri", "http://" + restCatalogBackendContainer.getRestCatalogEndpoint()) .put("iceberg.rest-catalog.vended-credentials-enabled", "true") .put("iceberg.writer-sort-buffer-size", "1MB") + .put("iceberg.allowed-extra-properties", "write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max") .put("fs.hadoop.enabled", "false") .put("fs.native-s3.enabled", "true") .put("s3.region", MINIO_REGION) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestTrinoRestCatalog.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestTrinoRestCatalog.java index 6e75f3aa8b78..398ee371ad1b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestTrinoRestCatalog.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestTrinoRestCatalog.java @@ -13,9 +13,7 @@ */ package io.trino.plugin.iceberg.catalog.rest; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.airlift.log.Logger; import io.trino.cache.EvictableCacheBuilder; import io.trino.metastore.TableInfo; import io.trino.plugin.hive.NodeVersion; @@ -27,12 +25,9 @@ import io.trino.spi.catalog.CatalogName; import io.trino.spi.connector.CatalogHandle; import io.trino.spi.connector.ConnectorMetadata; -import io.trino.spi.connector.ConnectorViewDefinition; -import io.trino.spi.connector.SchemaTableName; import io.trino.spi.security.PrincipalType; import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.type.TestingTypeManager; -import io.trino.spi.type.VarcharType; import org.apache.iceberg.exceptions.BadRequestException; import org.apache.iceberg.rest.DelegatingRestSessionCatalog; import org.apache.iceberg.rest.RESTSessionCatalog; @@ -40,11 +35,10 @@ import org.junit.jupiter.api.Test; import java.io.File; -import java.io.IOException; -import java.nio.file.Path; import java.util.Map; import java.util.Optional; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; import static io.airlift.json.JsonCodec.jsonCodec; import static io.trino.metastore.TableInfo.ExtendedRelationType.OTHER_VIEW; @@ -61,8 +55,6 @@ public class TestTrinoRestCatalog extends BaseTrinoCatalogTest { - private static final Logger LOG = Logger.get(TestTrinoRestCatalog.class); - @Override protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) { @@ -129,7 +121,8 @@ public void testNonLowercaseNamespace() Optional.empty(), false, _ -> false, - newDirectExecutorService()); + newDirectExecutorService(), + directExecutor()); assertThat(icebergMetadata.schemaExists(SESSION, namespace)).as("icebergMetadata.schemaExists(namespace)") .isTrue(); assertThat(icebergMetadata.schemaExists(SESSION, schema)).as("icebergMetadata.schemaExists(schema)") @@ -143,64 +136,6 @@ public void testNonLowercaseNamespace() } } - @Test - @Override - public void testView() - throws IOException - { - TrinoCatalog catalog = createTrinoCatalog(false); - Path tmpDirectory = java.nio.file.Files.createTempDirectory("iceberg_catalog_test_create_view_"); - tmpDirectory.toFile().deleteOnExit(); - - String namespace = "test_create_view_" + randomNameSuffix(); - String viewName = "viewName"; - String renamedViewName = "renamedViewName"; - SchemaTableName schemaTableName = new SchemaTableName(namespace, viewName); - SchemaTableName renamedSchemaTableName = new SchemaTableName(namespace, renamedViewName); - ConnectorViewDefinition viewDefinition = new ConnectorViewDefinition( - "SELECT name FROM local.tiny.nation", - Optional.empty(), - Optional.empty(), - ImmutableList.of( - new ConnectorViewDefinition.ViewColumn("name", VarcharType.createUnboundedVarcharType().getTypeId(), Optional.empty())), - Optional.empty(), - Optional.of(SESSION.getUser()), - false, - ImmutableList.of()); - - try { - catalog.createNamespace(SESSION, namespace, ImmutableMap.of(), new TrinoPrincipal(PrincipalType.USER, SESSION.getUser())); - catalog.createView(SESSION, schemaTableName, viewDefinition, false); - - assertThat(catalog.listTables(SESSION, Optional.of(namespace)).stream()).contains(new TableInfo(schemaTableName, OTHER_VIEW)); - - Map views = catalog.getViews(SESSION, Optional.of(schemaTableName.getSchemaName())); - assertThat(views).hasSize(1); - assertViewDefinition(views.get(schemaTableName), viewDefinition); - assertViewDefinition(catalog.getView(SESSION, schemaTableName).orElseThrow(), viewDefinition); - - catalog.renameView(SESSION, schemaTableName, renamedSchemaTableName); - assertThat(catalog.listTables(SESSION, Optional.of(namespace)).stream().map(TableInfo::tableName).toList()).doesNotContain(schemaTableName); - views = catalog.getViews(SESSION, Optional.of(schemaTableName.getSchemaName())); - assertThat(views).hasSize(1); - assertViewDefinition(views.get(renamedSchemaTableName), viewDefinition); - assertViewDefinition(catalog.getView(SESSION, renamedSchemaTableName).orElseThrow(), viewDefinition); - assertThat(catalog.getView(SESSION, schemaTableName)).isEmpty(); - - catalog.dropView(SESSION, renamedSchemaTableName); - assertThat(catalog.listTables(SESSION, Optional.empty()).stream().map(TableInfo::tableName).toList()) - .doesNotContain(renamedSchemaTableName); - } - finally { - try { - catalog.dropNamespace(SESSION, namespace); - } - catch (Exception e) { - LOG.warn("Failed to clean up namespace: %s", namespace); - } - } - } - @Test public void testPrefix() { @@ -218,4 +153,10 @@ public void testPrefix() .as("should fail as the prefix dev is not implemented for the current endpoint") .hasMessageContaining("Malformed request: No route for request: POST v1/dev/namespaces"); } + + @Override + protected TableInfo.ExtendedRelationType getViewType() + { + return OTHER_VIEW; + } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/snowflake/TestIcebergSnowflakeCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/snowflake/TestIcebergSnowflakeCatalogConnectorSmokeTest.java index 394e4c0b32a9..2488a1c683b5 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/snowflake/TestIcebergSnowflakeCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/snowflake/TestIcebergSnowflakeCatalogConnectorSmokeTest.java @@ -684,6 +684,14 @@ public void testSnowflakeNativeTable() .hasRootCauseMessage("SQL compilation error:\ninvalid parameter 'table ? is not a Snowflake iceberg table'"); } + @Test + @Override + public void testIcebergTablesFunction() + { + assertThatThrownBy(super::testIcebergTablesFunction) + .hasMessageContaining("schemaPath is not supported for Iceberg snowflake catalog"); + } + @Override protected boolean isFileSorted(Location path, String sortColumnName) { 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 b327be7c862d..ff5484d3fa04 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 @@ -28,7 +28,7 @@ public class NessieContainer { private static final Logger log = Logger.get(NessieContainer.class); - public static final String DEFAULT_IMAGE = "ghcr.io/projectnessie/nessie:0.101.1"; + public static final String DEFAULT_IMAGE = "ghcr.io/projectnessie/nessie:0.101.3"; public static final String DEFAULT_HOST_NAME = "nessie"; public static final String VERSION_STORE_TYPE = "IN_MEMORY"; 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 676f4cf1f7d2..37ebe5d04f8d 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 @@ -24,8 +24,8 @@ import io.trino.metadata.TestingFunctionResolution; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.HiveTransactionHandle; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.iceberg.ColumnIdentity; import io.trino.plugin.iceberg.IcebergColumnHandle; import io.trino.plugin.iceberg.IcebergConnector; diff --git a/plugin/trino-iceberg/src/test/java/org/apache/iceberg/snowflake/TestTrinoSnowflakeCatalog.java b/plugin/trino-iceberg/src/test/java/org/apache/iceberg/snowflake/TestTrinoSnowflakeCatalog.java index d445512ed2ae..936c3e251342 100644 --- a/plugin/trino-iceberg/src/test/java/org/apache/iceberg/snowflake/TestTrinoSnowflakeCatalog.java +++ b/plugin/trino-iceberg/src/test/java/org/apache/iceberg/snowflake/TestTrinoSnowflakeCatalog.java @@ -58,6 +58,7 @@ import java.util.Map; import java.util.Optional; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; import static io.airlift.json.JsonCodec.jsonCodec; import static io.trino.plugin.iceberg.catalog.snowflake.TestIcebergSnowflakeCatalogConnectorSmokeTest.S3_ACCESS_KEY; @@ -225,7 +226,8 @@ public void testNonLowercaseNamespace() Optional.empty(), false, _ -> false, - newDirectExecutorService()); + newDirectExecutorService(), + directExecutor()); assertThat(icebergMetadata.schemaExists(SESSION, namespace)).as("icebergMetadata.schemaExists(namespace)") .isTrue(); assertThat(icebergMetadata.schemaExists(SESSION, schema)).as("icebergMetadata.schemaExists(schema)") diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index 54b205c4fa3f..320ba396ceed 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 34c8183d7886..887db854e4e2 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 @@ -81,10 +81,13 @@ 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.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; +import static com.google.common.base.Verify.verify; +import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.ignite.IgniteTableProperties.PRIMARY_KEY_PROPERTY; import static io.trino.plugin.jdbc.ColumnMapping.longMapping; import static io.trino.plugin.jdbc.DecimalConfig.DecimalMapping.ALLOW_OVERFLOW; @@ -466,6 +469,26 @@ public boolean isLimitGuaranteed(ConnectorSession session) return true; } + @Override + public boolean supportsMerge() + { + return true; + } + + @Override + public List getPrimaryKeys(ConnectorSession session, RemoteTableName remoteTableName) + { + JdbcTableHandle plainTable = new JdbcTableHandle(remoteTableName.getSchemaTableName(), remoteTableName, Optional.empty()); + Map tableProperties = getTableProperties(session, plainTable); + Set primaryKey = ImmutableSet.copyOf(IgniteTableProperties.getPrimaryKey(tableProperties)); + List primaryKeys = getColumns(session, remoteTableName.getSchemaTableName(), remoteTableName) + .stream() + .filter(columnHandle -> primaryKey.contains(columnHandle.getColumnName().toLowerCase(ENGLISH))) + .collect(toImmutableList()); + verify(!primaryKeys.isEmpty(), "Ignite primary keys is empty"); + return primaryKeys; + } + @Override public boolean supportsTopN(ConnectorSession session, JdbcTableHandle handle, List sortOrder) { diff --git a/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteMergeTableHandle.java b/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteMergeTableHandle.java new file mode 100644 index 000000000000..7f027ed4ee5e --- /dev/null +++ b/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteMergeTableHandle.java @@ -0,0 +1,40 @@ +/* + * 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.ignite; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.trino.plugin.jdbc.JdbcColumnHandle; +import io.trino.plugin.jdbc.JdbcMergeTableHandle; +import io.trino.plugin.jdbc.JdbcTableHandle; +import io.trino.spi.connector.ColumnHandle; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class IgniteMergeTableHandle + extends JdbcMergeTableHandle +{ + @JsonCreator + public IgniteMergeTableHandle( + @JsonProperty("tableHandle") JdbcTableHandle tableHandle, + @JsonProperty("outputTableHandle") IgniteOutputTableHandle outputTableHandle, + @JsonProperty("primaryKeys") List primaryKeys, + @JsonProperty("dataColumns") List dataColumns, + @JsonProperty("updateCaseColumns") Map> updateCaseColumns) + { + super(tableHandle, outputTableHandle, primaryKeys, dataColumns, updateCaseColumns); + } +} diff --git a/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteMetadata.java b/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteMetadata.java index de60474d001d..283226d8506a 100644 --- a/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteMetadata.java +++ b/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteMetadata.java @@ -18,6 +18,7 @@ import io.trino.plugin.jdbc.DefaultJdbcMetadata; import io.trino.plugin.jdbc.JdbcClient; import io.trino.plugin.jdbc.JdbcColumnHandle; +import io.trino.plugin.jdbc.JdbcMergeTableHandle; import io.trino.plugin.jdbc.JdbcNamedRelationHandle; import io.trino.plugin.jdbc.JdbcQueryEventListener; import io.trino.plugin.jdbc.JdbcTableHandle; @@ -28,6 +29,7 @@ import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.connector.ConnectorInsertTableHandle; +import io.trino.spi.connector.ConnectorMergeTableHandle; import io.trino.spi.connector.ConnectorOutputMetadata; import io.trino.spi.connector.ConnectorOutputTableHandle; import io.trino.spi.connector.ConnectorSession; @@ -44,9 +46,11 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.jdbc.JdbcMetadata.getColumns; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; @@ -100,6 +104,9 @@ public ConnectorInsertTableHandle beginInsert(ConnectorSession session, Connecto ImmutableList.Builder columnJdbcTypeHandles = ImmutableList.builder(); for (ColumnHandle column : columns) { JdbcColumnHandle columnHandle = (JdbcColumnHandle) column; + if (IGNITE_DUMMY_ID.equalsIgnoreCase(columnHandle.getColumnName())) { + continue; + } columnNames.add(columnHandle.getColumnName()); columnTypes.add(columnHandle.getColumnType()); columnJdbcTypeHandles.add(columnHandle.getJdbcTypeHandle()); @@ -125,6 +132,48 @@ public Optional finishInsert( return Optional.empty(); } + @Override + public ConnectorMergeTableHandle beginMerge(ConnectorSession session, ConnectorTableHandle tableHandle, Map> updateColumnHandles, RetryMode retryMode) + { + JdbcTableHandle handle = (JdbcTableHandle) tableHandle; + JdbcMergeTableHandle mergeTableHandle = (JdbcMergeTableHandle) super.beginMerge(session, tableHandle, updateColumnHandles, retryMode); + + List primaryKeys = mergeTableHandle.getPrimaryKeys(); + List columns = igniteClient.getColumns(session, + handle.getRequiredNamedRelation().getSchemaTableName(), + handle.getRequiredNamedRelation().getRemoteTableName()).stream() + .filter(column -> !IGNITE_DUMMY_ID.equalsIgnoreCase(column.getColumnName())) + .collect(toImmutableList()); + + for (Collection updateColumns : updateColumnHandles.values()) { + for (ColumnHandle column : updateColumns) { + checkArgument(columns.contains(column), "the update column not found in the target table"); + checkArgument(!primaryKeys.contains(column), "Ignite does not allow update primary key"); + } + } + + if (handle.getColumns().isPresent()) { + handle = new JdbcTableHandle( + handle.getRelationHandle(), + handle.getConstraint(), + handle.getConstraintExpressions(), + handle.getSortOrder(), + handle.getLimit(), + Optional.of(columns), + handle.getOtherReferencedTables(), + handle.getNextSyntheticColumnId(), + handle.getAuthorization(), + handle.getUpdateAssignments()); + } + + return new IgniteMergeTableHandle( + handle, + (IgniteOutputTableHandle) mergeTableHandle.getOutputTableHandle(), + primaryKeys, + columns, + mergeTableHandle.getUpdateCaseColumns()); + } + @Override public ConnectorTableMetadata getTableMetadata(ConnectorSession session, ConnectorTableHandle table) { 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 a563121ed76a..142eaef4a076 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 @@ -14,6 +14,7 @@ package io.trino.plugin.ignite; import com.google.common.collect.ImmutableList; +import io.trino.Session; import io.trino.plugin.jdbc.BaseJdbcConnectorTest; import io.trino.sql.planner.plan.FilterNode; import io.trino.sql.planner.plan.TableScanNode; @@ -29,6 +30,7 @@ import java.util.Optional; import static com.google.common.base.Strings.nullToEmpty; +import static io.trino.plugin.jdbc.JdbcWriteSessionProperties.NON_TRANSACTIONAL_MERGE; import static io.trino.sql.planner.assertions.PlanMatchPattern.node; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; @@ -52,6 +54,15 @@ protected QueryRunner createQueryRunner() .build(); } + @Override + protected Session getSession() + { + Session session = super.getSession(); + return Session.builder(session) + .setCatalogSessionProperty(session.getCatalog().orElseThrow(), NON_TRANSACTIONAL_MERGE, "true") + .build(); + } + @Override protected SqlExecutor onRemoteDatabase() { @@ -64,7 +75,9 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) return switch (connectorBehavior) { case SUPPORTS_AGGREGATION_PUSHDOWN, SUPPORTS_JOIN_PUSHDOWN, + SUPPORTS_MERGE, SUPPORTS_PREDICATE_EXPRESSION_PUSHDOWN_WITH_LIKE, + SUPPORTS_ROW_LEVEL_UPDATE, SUPPORTS_TOPN_PUSHDOWN_WITH_VARCHAR -> true; case SUPPORTS_ADD_COLUMN_NOT_NULL_CONSTRAINT, SUPPORTS_ADD_COLUMN_WITH_COMMENT, @@ -96,8 +109,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) @Test public void testLikeWithEscape() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_like_with_escape", "(id int, a varchar(4))", List.of( @@ -127,8 +139,7 @@ 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, + try (TestTable table = newTrinoTable( "test_is_null_predicate_pushdown", "(a_int integer, a_varchar varchar(1))", List.of( @@ -145,8 +156,7 @@ 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, + try (TestTable table = newTrinoTable( "test_is_not_null_predicate_pushdown", "(a_int integer, a_varchar varchar(1))", List.of( @@ -163,8 +173,7 @@ public void testNotExpressionPushdown() { assertThat(query("SELECT nationkey FROM nation WHERE NOT(name LIKE '%A%')")).isFullyPushedDown(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_is_not_predicate_pushdown", "(a_int integer, a_varchar varchar(2))", List.of( @@ -319,8 +328,7 @@ public void testShowCreateTable() @Test public void testAvgDecimalExceedingSupportedPrecision() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_avg_decimal_exceeding_supported_precision", "(a decimal(38, 38), b bigint)", List.of( @@ -392,7 +400,7 @@ public void testDropAndAddColumnWithSameName() { // Override because Ignite can access old data after dropping and adding a column with same name executeExclusively(() -> { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_add_column", "AS SELECT 1 x, 2 y, 3 z")) { + try (TestTable table = newTrinoTable("test_drop_add_column", "AS SELECT 1 x, 2 y, 3 z")) { assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN y"); assertQuery("SELECT * FROM " + table.getName(), "VALUES (1, 3)"); diff --git a/plugin/trino-jmx/pom.xml b/plugin/trino-jmx/pom.xml index f0571da2c96e..9f6b5404064d 100644 --- a/plugin/trino-jmx/pom.xml +++ b/plugin/trino-jmx/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kafka-event-listener/pom.xml b/plugin/trino-kafka-event-listener/pom.xml index 78f3612fe0b1..95147ca0724a 100644 --- a/plugin/trino-kafka-event-listener/pom.xml +++ b/plugin/trino-kafka-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index 497e4b224f01..fa18036d378f 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index 9d171338ff8c..99f3beb33985 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index a88965bd4975..cb119e0db3b1 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 eec1ac45dd8a..139c9e7b132c 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 @@ -19,6 +19,7 @@ import io.airlift.slice.Slices; import io.trino.plugin.kudu.properties.ColumnDesign; import io.trino.plugin.kudu.properties.HashPartitionDefinition; +import io.trino.plugin.kudu.properties.KuduColumnProperties; import io.trino.plugin.kudu.properties.KuduTableProperties; import io.trino.plugin.kudu.properties.PartitionDesign; import io.trino.plugin.kudu.properties.RangePartition; @@ -416,7 +417,7 @@ private Schema buildSchema(List columns) private ColumnSchema toColumnSchema(ColumnMetadata columnMetadata) { String name = columnMetadata.getName(); - ColumnDesign design = KuduTableProperties.getColumnDesign(columnMetadata.getProperties()); + ColumnDesign design = KuduColumnProperties.getColumnDesign(columnMetadata.getProperties()); Type ktype = TypeHelper.toKuduClientType(columnMetadata.getType()); ColumnSchemaBuilder builder = new ColumnSchemaBuilder(name, ktype); builder.key(design.isPrimaryKey()).nullable(design.isNullable()); @@ -440,7 +441,7 @@ private void setCompression(String name, ColumnSchemaBuilder builder, ColumnDesi { if (design.getCompression() != null) { try { - CompressionAlgorithm algorithm = KuduTableProperties.lookupCompression(design.getCompression()); + CompressionAlgorithm algorithm = KuduColumnProperties.lookupCompression(design.getCompression()); builder.compressionAlgorithm(algorithm); } catch (IllegalArgumentException e) { @@ -453,7 +454,7 @@ private void setEncoding(String name, ColumnSchemaBuilder builder, ColumnDesign { if (design.getEncoding() != null) { try { - Encoding encoding = KuduTableProperties.lookupEncoding(design.getEncoding()); + Encoding encoding = KuduColumnProperties.lookupEncoding(design.getEncoding()); builder.encoding(encoding); } catch (IllegalArgumentException e) { diff --git a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduConnector.java b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduConnector.java index 062533db4502..d798e1923b86 100755 --- a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduConnector.java +++ b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduConnector.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import io.airlift.bootstrap.LifeCycleManager; +import io.trino.plugin.kudu.properties.KuduColumnProperties; import io.trino.plugin.kudu.properties.KuduTableProperties; import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorMetadata; @@ -44,6 +45,7 @@ public class KuduConnector private final ConnectorSplitManager splitManager; private final ConnectorPageSourceProvider pageSourceProvider; private final KuduTableProperties tableProperties; + private final KuduColumnProperties columnProperties; private final ConnectorPageSinkProvider pageSinkProvider; private final Set procedures; private final ConnectorNodePartitioningProvider nodePartitioningProvider; @@ -55,6 +57,7 @@ public KuduConnector( KuduMetadata metadata, ConnectorSplitManager splitManager, KuduTableProperties tableProperties, + KuduColumnProperties columnProperties, ConnectorPageSourceProvider pageSourceProvider, ConnectorPageSinkProvider pageSinkProvider, Set procedures, @@ -66,6 +69,7 @@ public KuduConnector( this.splitManager = requireNonNull(splitManager, "splitManager is null"); this.pageSourceProvider = requireNonNull(pageSourceProvider, "pageSourceProvider is null"); this.tableProperties = requireNonNull(tableProperties, "tableProperties is null"); + this.columnProperties = requireNonNull(columnProperties, "columnProperties is null"); this.pageSinkProvider = requireNonNull(pageSinkProvider, "pageSinkProvider is null"); this.procedures = ImmutableSet.copyOf(requireNonNull(procedures, "procedures is null")); this.nodePartitioningProvider = requireNonNull(nodePartitioningProvider, "nodePartitioningProvider is null"); @@ -112,7 +116,7 @@ public List> getTableProperties() @Override public List> getColumnProperties() { - return tableProperties.getColumnProperties(); + return columnProperties.getColumnProperties(); } @Override 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 511a424be7b1..49d5bb64cc54 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 @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; import io.airlift.slice.Slice; +import io.trino.plugin.kudu.properties.KuduColumnProperties; import io.trino.plugin.kudu.properties.KuduTableProperties; import io.trino.plugin.kudu.properties.PartitionDesign; import io.trino.spi.TrinoException; @@ -139,24 +140,24 @@ private ColumnMetadata getColumnMetadata(ColumnSchema column) Map properties = new LinkedHashMap<>(); StringBuilder extra = new StringBuilder(); if (column.isKey()) { - properties.put(KuduTableProperties.PRIMARY_KEY, true); + properties.put(KuduColumnProperties.PRIMARY_KEY, true); extra.append("primary_key, "); } if (column.isNullable()) { - properties.put(KuduTableProperties.NULLABLE, true); + properties.put(KuduColumnProperties.NULLABLE, true); extra.append("nullable, "); } - String encoding = KuduTableProperties.lookupEncodingString(column.getEncoding()); + String encoding = KuduColumnProperties.lookupEncodingString(column.getEncoding()); if (column.getEncoding() != ColumnSchema.Encoding.AUTO_ENCODING) { - properties.put(KuduTableProperties.ENCODING, encoding); + properties.put(KuduColumnProperties.ENCODING, encoding); } extra.append("encoding=").append(encoding).append(", "); - String compression = KuduTableProperties.lookupCompressionString(column.getCompressionAlgorithm()); + String compression = KuduColumnProperties.lookupCompressionString(column.getCompressionAlgorithm()); if (column.getCompressionAlgorithm() != ColumnSchema.CompressionAlgorithm.DEFAULT_COMPRESSION) { - properties.put(KuduTableProperties.COMPRESSION, compression); + properties.put(KuduColumnProperties.COMPRESSION, compression); } extra.append("compression=").append(compression); @@ -374,7 +375,7 @@ public ConnectorOutputTableHandle beginCreateTable( String rowId = ROW_ID; List copy = new ArrayList<>(tableMetadata.getColumns()); Map columnProperties = new HashMap<>(); - columnProperties.put(KuduTableProperties.PRIMARY_KEY, true); + columnProperties.put(KuduColumnProperties.PRIMARY_KEY, true); copy.add(0, ColumnMetadata.builder() .setName(rowId) .setType(VarcharType.VARCHAR) diff --git a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduModule.java b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduModule.java index 6dd9a977e643..628f21274fd7 100755 --- a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduModule.java +++ b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduModule.java @@ -21,6 +21,7 @@ import io.trino.plugin.base.classloader.ClassLoaderSafeNodePartitioningProvider; import io.trino.plugin.base.classloader.ForClassLoaderSafe; import io.trino.plugin.kudu.procedures.RangePartitionProcedures; +import io.trino.plugin.kudu.properties.KuduColumnProperties; import io.trino.plugin.kudu.properties.KuduTableProperties; import io.trino.spi.connector.ConnectorNodePartitioningProvider; import io.trino.spi.connector.ConnectorPageSinkProvider; @@ -50,6 +51,7 @@ protected void setup(Binder binder) binder.bind(KuduConnector.class).in(Scopes.SINGLETON); binder.bind(KuduMetadata.class).in(Scopes.SINGLETON); binder.bind(KuduTableProperties.class).in(Scopes.SINGLETON); + binder.bind(KuduColumnProperties.class).in(Scopes.SINGLETON); binder.bind(ConnectorSplitManager.class).to(KuduSplitManager.class).in(Scopes.SINGLETON); binder.bind(ConnectorPageSourceProvider.class).to(KuduPageSourceProvider.class) .in(Scopes.SINGLETON); diff --git a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/properties/KuduColumnProperties.java b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/properties/KuduColumnProperties.java new file mode 100644 index 000000000000..778d160276bc --- /dev/null +++ b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/properties/KuduColumnProperties.java @@ -0,0 +1,151 @@ +/* + * 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.kudu.properties; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; +import io.trino.spi.session.PropertyMetadata; +import org.apache.kudu.ColumnSchema; + +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static io.trino.spi.session.PropertyMetadata.booleanProperty; +import static io.trino.spi.session.PropertyMetadata.stringProperty; +import static java.util.Objects.requireNonNull; + +public final class KuduColumnProperties +{ + public static final String PRIMARY_KEY = "primary_key"; + public static final String NULLABLE = "nullable"; + public static final String ENCODING = "encoding"; + public static final String COMPRESSION = "compression"; + + private final List> columnProperties; + + @Inject + public KuduColumnProperties() + { + columnProperties = ImmutableList.>builder() + .add(booleanProperty( + PRIMARY_KEY, + "If column belongs to primary key", + false, + false)) + .add(booleanProperty( + NULLABLE, + "If column can be set to null", + false, + false)) + .add(stringProperty( + ENCODING, + "Optional specification of the column encoding. Otherwise default encoding is applied.", + null, + false)) + .add(stringProperty( + COMPRESSION, + "Optional specification of the column compression. Otherwise default compression is applied.", + null, + false)) + .build(); + } + + public List> getColumnProperties() + { + return columnProperties; + } + + public static ColumnDesign getColumnDesign(Map columnProperties) + { + requireNonNull(columnProperties); + if (columnProperties.isEmpty()) { + return ColumnDesign.DEFAULT; + } + + ColumnDesign design = new ColumnDesign(); + Boolean key = (Boolean) columnProperties.get(PRIMARY_KEY); + if (key != null) { + design.setPrimaryKey(key); + } + + Boolean nullable = (Boolean) columnProperties.get(NULLABLE); + if (nullable != null) { + design.setNullable(nullable); + } + + String encoding = (String) columnProperties.get(ENCODING); + if (encoding != null) { + design.setEncoding(encoding); + } + + String compression = (String) columnProperties.get(COMPRESSION); + if (compression != null) { + design.setCompression(compression); + } + return design; + } + + public static ColumnSchema.CompressionAlgorithm lookupCompression(String compression) + { + return switch (compression.toLowerCase(Locale.ENGLISH)) { + case "default", "default_compression" -> ColumnSchema.CompressionAlgorithm.DEFAULT_COMPRESSION; + case "no", "no_compression" -> ColumnSchema.CompressionAlgorithm.NO_COMPRESSION; + case "lz4" -> ColumnSchema.CompressionAlgorithm.LZ4; + case "snappy" -> ColumnSchema.CompressionAlgorithm.SNAPPY; + case "zlib" -> ColumnSchema.CompressionAlgorithm.ZLIB; + default -> throw new IllegalArgumentException(); + }; + } + + public static String lookupCompressionString(ColumnSchema.CompressionAlgorithm algorithm) + { + return switch (algorithm) { + case DEFAULT_COMPRESSION -> "default"; + case NO_COMPRESSION -> "no"; + case LZ4 -> "lz4"; + case SNAPPY -> "snappy"; + case ZLIB -> "zlib"; + default -> "unknown"; + }; + } + + public static ColumnSchema.Encoding lookupEncoding(String encoding) + { + return switch (encoding.toLowerCase(Locale.ENGLISH)) { + case "auto", "auto_encoding" -> ColumnSchema.Encoding.AUTO_ENCODING; + case "bitshuffle", "bit_shuffle" -> ColumnSchema.Encoding.BIT_SHUFFLE; + case "dictionary", "dict_encoding" -> ColumnSchema.Encoding.DICT_ENCODING; + case "plain", "plain_encoding" -> ColumnSchema.Encoding.PLAIN_ENCODING; + case "prefix", "prefix_encoding" -> ColumnSchema.Encoding.PREFIX_ENCODING; + case "runlength", "run_length", "run length", "rle" -> ColumnSchema.Encoding.RLE; + case "group_varint" -> ColumnSchema.Encoding.GROUP_VARINT; + default -> throw new IllegalArgumentException(); + }; + } + + public static String lookupEncodingString(ColumnSchema.Encoding encoding) + { + return switch (encoding) { + case AUTO_ENCODING -> "auto"; + case BIT_SHUFFLE -> "bitshuffle"; + case DICT_ENCODING -> "dictionary"; + case PLAIN_ENCODING -> "plain"; + case PREFIX_ENCODING -> "prefix"; + case RLE -> "runlength"; + case GROUP_VARINT -> "group_varint"; + default -> "unknown"; + }; + } +} diff --git a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/properties/KuduTableProperties.java b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/properties/KuduTableProperties.java index 909a3f7cc86f..588bc0f9b985 100644 --- a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/properties/KuduTableProperties.java +++ b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/properties/KuduTableProperties.java @@ -36,14 +36,12 @@ import java.util.Base64; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.base.util.JsonUtils.parseJson; import static io.trino.spi.StandardErrorCode.GENERIC_USER_ERROR; -import static io.trino.spi.session.PropertyMetadata.booleanProperty; import static io.trino.spi.session.PropertyMetadata.integerProperty; import static io.trino.spi.session.PropertyMetadata.stringProperty; import static io.trino.spi.type.VarcharType.VARCHAR; @@ -59,10 +57,6 @@ public final class KuduTableProperties public static final String PARTITION_BY_RANGE_COLUMNS = "partition_by_range_columns"; public static final String RANGE_PARTITIONS = "range_partitions"; public static final String NUM_REPLICAS = "number_of_replicas"; - public static final String PRIMARY_KEY = "primary_key"; - public static final String NULLABLE = "nullable"; - public static final String ENCODING = "encoding"; - public static final String COMPRESSION = "compression"; private static final ObjectMapper mapper = new ObjectMapper(); @@ -70,8 +64,6 @@ public final class KuduTableProperties private final List> tableProperties; - private final List> columnProperties; - @Inject public KuduTableProperties() { @@ -129,28 +121,6 @@ public KuduTableProperties() "Initial range partitions as JSON", null, false)); - - columnProperties = ImmutableList.of( - booleanProperty( - PRIMARY_KEY, - "If column belongs to primary key", - false, - false), - booleanProperty( - NULLABLE, - "If column can be set to null", - false, - false), - stringProperty( - ENCODING, - "Optional specification of the column encoding. Otherwise default encoding is applied.", - null, - false), - stringProperty( - COMPRESSION, - "Optional specification of the column compression. Otherwise default compression is applied.", - null, - false)); } public List> getTableProperties() @@ -158,11 +128,6 @@ public List> getTableProperties() return tableProperties; } - public List> getColumnProperties() - { - return columnProperties; - } - public static PartitionDesign getPartitionDesign(Map tableProperties) { requireNonNull(tableProperties); @@ -196,36 +161,6 @@ else if (!hashColumns2.isEmpty()) { return design; } - public static ColumnDesign getColumnDesign(Map columnProperties) - { - requireNonNull(columnProperties); - if (columnProperties.isEmpty()) { - return ColumnDesign.DEFAULT; - } - - ColumnDesign design = new ColumnDesign(); - Boolean key = (Boolean) columnProperties.get(PRIMARY_KEY); - if (key != null) { - design.setPrimaryKey(key); - } - - Boolean nullable = (Boolean) columnProperties.get(NULLABLE); - if (nullable != null) { - design.setNullable(nullable); - } - - String encoding = (String) columnProperties.get(ENCODING); - if (encoding != null) { - design.setEncoding(encoding); - } - - String compression = (String) columnProperties.get(COMPRESSION); - if (compression != null) { - design.setCompression(compression); - } - return design; - } - private static HashPartitionDefinition getHashPartitionDefinition(Map tableProperties, List columns, String bucketPropertyName) { Integer hashBuckets = (Integer) tableProperties.get(bucketPropertyName); @@ -542,56 +477,4 @@ private static void handleInvalidValue(String name, Type type, Object obj) { throw new IllegalStateException("Invalid value " + obj + " for column " + name + " of type " + type); } - - public static ColumnSchema.CompressionAlgorithm lookupCompression(String compression) - { - return switch (compression.toLowerCase(Locale.ENGLISH)) { - case "default", "default_compression" -> ColumnSchema.CompressionAlgorithm.DEFAULT_COMPRESSION; - case "no", "no_compression" -> ColumnSchema.CompressionAlgorithm.NO_COMPRESSION; - case "lz4" -> ColumnSchema.CompressionAlgorithm.LZ4; - case "snappy" -> ColumnSchema.CompressionAlgorithm.SNAPPY; - case "zlib" -> ColumnSchema.CompressionAlgorithm.ZLIB; - default -> throw new IllegalArgumentException(); - }; - } - - public static String lookupCompressionString(ColumnSchema.CompressionAlgorithm algorithm) - { - return switch (algorithm) { - case DEFAULT_COMPRESSION -> "default"; - case NO_COMPRESSION -> "no"; - case LZ4 -> "lz4"; - case SNAPPY -> "snappy"; - case ZLIB -> "zlib"; - default -> "unknown"; - }; - } - - public static ColumnSchema.Encoding lookupEncoding(String encoding) - { - return switch (encoding.toLowerCase(Locale.ENGLISH)) { - case "auto", "auto_encoding" -> ColumnSchema.Encoding.AUTO_ENCODING; - case "bitshuffle", "bit_shuffle" -> ColumnSchema.Encoding.BIT_SHUFFLE; - case "dictionary", "dict_encoding" -> ColumnSchema.Encoding.DICT_ENCODING; - case "plain", "plain_encoding" -> ColumnSchema.Encoding.PLAIN_ENCODING; - case "prefix", "prefix_encoding" -> ColumnSchema.Encoding.PREFIX_ENCODING; - case "runlength", "run_length", "run length", "rle" -> ColumnSchema.Encoding.RLE; - case "group_varint" -> ColumnSchema.Encoding.GROUP_VARINT; - default -> throw new IllegalArgumentException(); - }; - } - - public static String lookupEncodingString(ColumnSchema.Encoding encoding) - { - return switch (encoding) { - case AUTO_ENCODING -> "auto"; - case BIT_SHUFFLE -> "bitshuffle"; - case DICT_ENCODING -> "dictionary"; - case PLAIN_ENCODING -> "plain"; - case PREFIX_ENCODING -> "prefix"; - case RLE -> "runlength"; - case GROUP_VARINT -> "group_varint"; - default -> "unknown"; - }; - } } 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 e1c7579e9c01..ef506197672b 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 @@ -523,8 +523,7 @@ public void testAddColumnWithComment() public void testAddColumnWithCommentSpecialCharacter(String comment) { // Override because Kudu connector doesn't support creating a new table without partition columns - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "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)); @@ -557,7 +556,7 @@ public void testAddColumnWithDecimal() @Test public void testInsertIntoTableHavingRowUuid() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_", " AS SELECT * FROM region WITH NO DATA")) { + try (TestTable table = newTrinoTable("test_insert_", " AS SELECT * FROM region WITH NO DATA")) { assertUpdate("INSERT INTO " + table.getName() + " SELECT * FROM region", 5); assertThat(query("SELECT * FROM " + table.getName())) @@ -570,7 +569,7 @@ public void testInsertIntoTableHavingRowUuid() public void testInsertUnicode() { // TODO Remove this overriding test once kudu connector can create tables with default partitions - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_unicode_", + try (TestTable table = newTrinoTable("test_insert_unicode_", "(test varchar(50) WITH (primary_key=true)) " + "WITH (partition_by_hash_columns = ARRAY['test'], partition_by_hash_buckets = 2)")) { assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'Hello', U&'hello\\6d4B\\8Bd5world\\7F16\\7801' ", 2); @@ -578,7 +577,7 @@ public void testInsertUnicode() .containsExactlyInAnyOrder("Hello", "hello测试world编码"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_unicode_", + try (TestTable table = newTrinoTable("test_insert_unicode_", "(test varchar(50) WITH (primary_key=true)) " + "WITH (partition_by_hash_columns = ARRAY['test'], partition_by_hash_buckets = 2)")) { assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'aa', 'bé'", 2); @@ -589,7 +588,7 @@ public void testInsertUnicode() assertQueryReturnsEmptyResult("SELECT test FROM " + table.getName() + " WHERE test = 'ba'"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_unicode_", + try (TestTable table = newTrinoTable("test_insert_unicode_", "(test varchar(50) WITH (primary_key=true)) " + "WITH (partition_by_hash_columns = ARRAY['test'], partition_by_hash_buckets = 2)")) { assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'a', 'é'", 2); @@ -606,7 +605,7 @@ public void testInsertUnicode() public void testInsertHighestUnicodeCharacter() { // TODO Remove this overriding test once kudu connector can create tables with default partitions - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_unicode_", + try (TestTable table = newTrinoTable("test_insert_unicode_", "(test varchar(50) WITH (primary_key=true)) " + "WITH (partition_by_hash_columns = ARRAY['test'], partition_by_hash_buckets = 2)")) { assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'Hello', U&'hello\\6d4B\\8Bd5\\+10FFFFworld\\7F16\\7801' ", 2); @@ -620,7 +619,7 @@ public void testInsertHighestUnicodeCharacter() public void testInsertNegativeDate() { // TODO Remove this overriding test once kudu connector can create tables with default partitions - try (TestTable table = new TestTable(getQueryRunner()::execute, "insert_date", + try (TestTable table = newTrinoTable("insert_date", "(dt DATE WITH (primary_key=true)) " + "WITH (partition_by_hash_columns = ARRAY['dt'], partition_by_hash_buckets = 2)")) { assertQueryFails(format("INSERT INTO %s VALUES (DATE '-0001-01-01')", table.getName()), errorMessageForInsertNegativeDate("-0001-01-01")); @@ -695,7 +694,7 @@ public void testDeleteWithLike() protected TestTable createTableWithOneIntegerColumn(String namePrefix) { // TODO Remove this overriding method once kudu connector can create tables with default partitions - return new TestTable(getQueryRunner()::execute, namePrefix, + return newTrinoTable(namePrefix, "(col integer WITH (primary_key=true)) " + "WITH (partition_by_hash_columns = ARRAY['col'], partition_by_hash_buckets = 2)"); } @@ -995,6 +994,20 @@ public void testUpdateRowConcurrently() abort("Kudu doesn't support concurrent update of different columns in a row"); } + @Test + @Override + protected void testUpdateWithSubquery() + { + withTableName("test_update_with_subquery", tableName -> { + createTableForWrites("CREATE TABLE %s " + ORDER_COLUMNS, tableName, Optional.empty()); + assertUpdate("INSERT INTO " + tableName + " SELECT * FROM orders", 15000); + + assertQuery("SELECT count(*) FROM " + tableName + " WHERE shippriority = 101 AND custkey = (SELECT min(custkey) FROM customer)", "VALUES 0"); + assertUpdate("UPDATE " + tableName + " SET shippriority = 101 WHERE custkey = (SELECT min(custkey) FROM customer)", 9); + assertQuery("SELECT count(*) FROM " + tableName + " WHERE shippriority = 101 AND custkey = (SELECT min(custkey) FROM customer)", "VALUES 9"); + }); + } + @Test @Override public void testCreateTableWithTableComment() @@ -1013,7 +1026,7 @@ public void testCreateTableWithTableComment() 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, + try (TestTable table = newTrinoTable( "test_create_", "(a bigint WITH (primary_key=true)) COMMENT " + varcharLiteral(comment) + "WITH (partition_by_hash_columns = ARRAY['a'], partition_by_hash_buckets = 2)")) { @@ -1047,7 +1060,6 @@ protected Optional filterDataMappingSmokeTestData(DataMapp return Optional.of(dataMappingTestSetup); } - @Test @Override protected TestTable createTableWithDefaultColumns() { diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index f17bf53033ba..3aac1b576a39 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 8c1bac7c0115..c299e9c9a510 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 @@ -190,7 +190,7 @@ public void testAddNotNullColumn() .isInstanceOf(AssertionError.class) .hasMessage("Should fail to add not null column without a default value to a non-empty table"); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_nn_col", "(a_varchar varchar)")) { + try (TestTable table = newTrinoTable("test_add_nn_col", "(a_varchar varchar)")) { String tableName = table.getName(); assertUpdate("INSERT INTO " + tableName + " VALUES ('a')", 1); @@ -269,7 +269,7 @@ public void testDeleteWithLike() @Override public void testInsertIntoNotNullColumn() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "insert_not_null", "(nullable_col INTEGER, not_null_col INTEGER NOT NULL)")) { + try (TestTable table = newTrinoTable("insert_not_null", "(nullable_col INTEGER, not_null_col INTEGER NOT NULL)")) { assertUpdate(format("INSERT INTO %s (not_null_col) VALUES (2)", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES (NULL, 2)"); assertQueryFails(format("INSERT INTO %s (nullable_col) VALUES (1)", table.getName()), errorMessageForInsertIntoNotNullColumn("not_null_col")); @@ -282,7 +282,7 @@ public void testInsertIntoNotNullColumn() assertQueryFails(format("INSERT INTO %s (nullable_col) SELECT nationkey FROM nation WHERE regionkey < 0", table.getName()), ".*Field 'not_null_col' doesn't have a default value.*"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "commuted_not_null", "(nullable_col BIGINT, not_null_col BIGINT NOT NULL)")) { + try (TestTable table = newTrinoTable("commuted_not_null", "(nullable_col BIGINT, not_null_col BIGINT NOT NULL)")) { assertUpdate(format("INSERT INTO %s (not_null_col) VALUES (2)", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES (NULL, 2)"); // This is enforced by the engine and not the connector diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbFailureRecoveryTest.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbFailureRecoveryTest.java index e8eb5128ba7d..dcde5b95b11d 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbFailureRecoveryTest.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbFailureRecoveryTest.java @@ -26,9 +26,6 @@ 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 BaseMariaDbFailureRecoveryTest extends BaseJdbcFailureRecoveryTest { @@ -55,14 +52,6 @@ protected QueryRunner createQueryRunner(List> requiredTpchTables, M .build(); } - @Test - @Override - protected void testUpdateWithSubquery() - { - assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - abort("skipped"); - } - @Test @Override protected void testUpdate() 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 8ec8b95227c7..bdb1dd2e737a 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 @@ -627,7 +627,7 @@ private void testDate(ZoneId sessionZone) @Test public void testUnsupportedDate() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_negative_date", "(dt DATE)")) { + try (TestTable table = newTrinoTable("test_negative_date", "(dt DATE)")) { assertQueryFails(format("INSERT INTO %s VALUES (DATE '-0001-01-01')", table.getName()), ".*Failed to insert data.*"); assertQueryFails(format("INSERT INTO %s VALUES (DATE '10000-01-01')", table.getName()), ".*Failed to insert data.*"); } @@ -777,7 +777,7 @@ private void testTimestamp(ZoneId sessionZone) @Test public void testIncorrectTimestamp() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_incorrect_timestamp", "(dt TIMESTAMP)")) { + try (TestTable table = newTrinoTable("test_incorrect_timestamp", "(dt TIMESTAMP)")) { assertQueryFails(format("INSERT INTO %s VALUES (TIMESTAMP '1970-01-01 00:00:00.000')", table.getName()), ".*Failed to insert data.*"); assertQueryFails(format("INSERT INTO %s VALUES (TIMESTAMP '2038-01-19 03:14:08.000')", table.getName()), ".*Failed to insert data.*"); } diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index 4d642b657707..1e52e1771cce 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 abc54b106411..f1d71b812e1e 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 @@ -607,7 +607,7 @@ public void testRenameView() @Test void testInsertAfterTruncate() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_truncate", "AS SELECT 1 x")) { + try (TestTable table = newTrinoTable("test_truncate", "AS SELECT 1 x")) { assertUpdate("TRUNCATE TABLE " + table.getName()); assertQueryReturnsEmptyResult("SELECT * FROM " + table.getName()); diff --git a/plugin/trino-ml/pom.xml b/plugin/trino-ml/pom.xml index 6023d4b10550..71bb45b39476 100644 --- a/plugin/trino-ml/pom.xml +++ b/plugin/trino-ml/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index c2d57fb6ceaa..e9ffb478f544 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/BaseMongoConnectorSmokeTest.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/BaseMongoConnectorSmokeTest.java index f39404a42ea0..b5de69af1d6d 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/BaseMongoConnectorSmokeTest.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/BaseMongoConnectorSmokeTest.java @@ -42,8 +42,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) @Test public void testProjectionPushdown() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_projection_pushdown_multiple_rows_", "(id INT, nested1 ROW(child1 INT, child2 VARCHAR))", ImmutableList.of( @@ -60,8 +59,7 @@ public void testProjectionPushdown() @Test public void testReadDottedField() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_read_dotted_field_", "(root ROW(\"dotted.field\" VARCHAR, field VARCHAR))", ImmutableList.of("ROW(ROW('foo', 'bar'))"))) { @@ -76,8 +74,7 @@ public void testReadDottedField() @Test public void testReadDollarPrefixedField() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_read_dotted_field_", "(root ROW(\"$field1\" VARCHAR, field2 VARCHAR))", ImmutableList.of("ROW(ROW('foo', 'bar'))"))) { @@ -92,8 +89,7 @@ public void testReadDollarPrefixedField() @Test public void testProjectionPushdownWithHighlyNestedData() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_projection_pushdown_highly_nested_data_", "(id INT, row1_t ROW(f1 INT, f2 INT, row2_t ROW (f1 INT, f2 INT, row3_t ROW(f1 INT, f2 INT))))", ImmutableList.of("(1, ROW(2, 3, ROW(4, 5, ROW(6, 7))))", 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 fd85c4abb8a6..512da391977c 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 @@ -356,7 +356,7 @@ public void testPredicatePushdown() private void testPredicatePushdown(String value) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_predicate_pushdown", "AS SELECT %s col".formatted(value))) { + try (TestTable table = newTrinoTable("test_predicate_pushdown", "AS SELECT %s col".formatted(value))) { testPredicatePushdown(table.getName(), "col = " + value); testPredicatePushdown(table.getName(), "col != " + value); testPredicatePushdown(table.getName(), "col < " + value); @@ -380,7 +380,7 @@ public void testPredicatePushdownDoubleType() private void testPredicatePushdownFloatingPoint(String value) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_floating_point_pushdown", "AS SELECT %s col".formatted(value))) { + try (TestTable table = newTrinoTable("test_floating_point_pushdown", "AS SELECT %s col".formatted(value))) { assertThat(query("SELECT * FROM " + table.getName() + " WHERE col = " + value)) .isFullyPushedDown(); assertThat(query("SELECT * FROM " + table.getName() + " WHERE col <= " + value)) @@ -402,8 +402,7 @@ private void testPredicatePushdownFloatingPoint(String value) @Test public void testPredicatePushdownCharWithPaddedSpace() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_predicate_pushdown_char_with_padded_space", "(k, v) AS VALUES" + " (-1, CAST(NULL AS char(3))), " + @@ -436,8 +435,7 @@ public void testPredicatePushdownCharWithPaddedSpace() public void testPredicatePushdownMultipleNotEquals() { // Regression test for https://github.com/trinodb/trino/issues/19404 - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_predicate_pushdown_with_multiple_not_equals", "(id, value) AS VALUES (1, 10), (2, 20), (3, 30)")) { assertThat(query("SELECT * FROM " + table.getName() + " WHERE id != 1 AND value != 20")) @@ -449,8 +447,7 @@ public void testPredicatePushdownMultipleNotEquals() @Test public void testHighPrecisionDecimalPredicate() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_high_precision_decimal_predicate", "(col DECIMAL(34, 0))", Arrays.asList("decimal '3141592653589793238462643383279502'", null))) { @@ -1354,8 +1351,7 @@ private void testFiltersOnDereferenceColumnReadsLessData(String expectedValue, S .setCatalogSessionProperty(getSession().getCatalog().orElseThrow(), "projection_pushdown_enabled", "false") .build(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "filter_on_projection_columns", format("(col_0 ROW(col_1 %1$s, col_2 ROW(col_3 %1$s, col_4 ROW(col_5 %1$s))))", expectedType))) { assertUpdate(format("INSERT INTO %s VALUES NULL", table.getName()), 1); 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 5d98edd7db21..b3346797f3fb 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 @@ -398,7 +398,7 @@ public void testArray() public void testArrayNulls() { // Verify only SELECT instead of using SqlDataTypeTest because array comparison not supported for arrays with null elements - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_array_nulls", "(c1 ARRAY(boolean), c2 ARRAY(varchar), c3 ARRAY(varchar))", ImmutableList.of("(NULL, ARRAY[NULL], ARRAY['foo', NULL, 'bar', NULL])"))) { + try (TestTable table = newTrinoTable("test_array_nulls", "(c1 ARRAY(boolean), c2 ARRAY(varchar), c3 ARRAY(varchar))", ImmutableList.of("(NULL, ARRAY[NULL], ARRAY['foo', NULL, 'bar', NULL])"))) { assertThat(query("SELECT c1 FROM " + table.getName())).matches("VALUES CAST(NULL AS ARRAY(boolean))"); assertThat(query("SELECT c2 FROM " + table.getName())).matches("VALUES CAST(ARRAY[NULL] AS ARRAY(varchar))"); assertThat(query("SELECT c3 FROM " + table.getName())).matches("VALUES CAST(ARRAY['foo', NULL, 'bar', NULL] AS ARRAY(varchar))"); diff --git a/plugin/trino-mysql-event-listener/pom.xml b/plugin/trino-mysql-event-listener/pom.xml index fab9d3498f2a..94da473f2bc4 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 - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index c54961744c11..7ae932343e81 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 31e3801153fb..d030e98a11fa 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 @@ -271,7 +271,7 @@ public void testAddNotNullColumn() .isInstanceOf(AssertionError.class) .hasMessage("Should fail to add not null column without a default value to a non-empty table"); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_nn_col", "(a_varchar varchar)")) { + try (TestTable table = newTrinoTable("test_add_nn_col", "(a_varchar varchar)")) { String tableName = table.getName(); assertUpdate("INSERT INTO " + tableName + " VALUES ('a')", 1); 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 1b474e83f11c..494e04deead5 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 @@ -26,9 +26,6 @@ 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 BaseMySqlFailureRecoveryTest extends BaseJdbcFailureRecoveryTest { @@ -58,14 +55,6 @@ protected QueryRunner createQueryRunner( .build(); } - @Test - @Override - protected void testUpdateWithSubquery() - { - assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - abort("skipped"); - } - @Test @Override protected void testUpdate() diff --git a/plugin/trino-opa/pom.xml b/plugin/trino-opa/pom.xml index 331e73f8369d..77f8d21b8559 100644 --- a/plugin/trino-opa/pom.xml +++ b/plugin/trino-opa/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-opa/src/test/java/io/trino/plugin/opa/OpaContainer.java b/plugin/trino-opa/src/test/java/io/trino/plugin/opa/OpaContainer.java index 6adbd04fe5a4..7ab583215939 100644 --- a/plugin/trino-opa/src/test/java/io/trino/plugin/opa/OpaContainer.java +++ b/plugin/trino-opa/src/test/java/io/trino/plugin/opa/OpaContainer.java @@ -38,7 +38,7 @@ public class OpaContainer public OpaContainer() { - this.container = new GenericContainer<>(DockerImageName.parse("openpolicyagent/opa:latest")) + this.container = new GenericContainer<>(DockerImageName.parse("openpolicyagent/opa:0.70.0")) .withCommand("run", "--server", "--addr", ":%d".formatted(OPA_PORT), "--set", "decision_logs.console=true") .withExposedPorts(OPA_PORT) .waitingFor(Wait.forListeningPort()); diff --git a/plugin/trino-openlineage/pom.xml b/plugin/trino-openlineage/pom.xml index 4faaed1daccb..3d54ff07a318 100644 --- a/plugin/trino-openlineage/pom.xml +++ b/plugin/trino-openlineage/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -61,7 +61,7 @@ io.openlineage openlineage-java - 1.25.0 + 1.26.0 diff --git a/plugin/trino-opensearch/pom.xml b/plugin/trino-opensearch/pom.xml index f9d066bec3f2..a174da0fea2b 100644 --- a/plugin/trino-opensearch/pom.xml +++ b/plugin/trino-opensearch/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index 0d9e6086753f..c8f0865e7225 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 ba076101d7b4..c70e80df5d53 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 @@ -692,7 +692,7 @@ private void testDate(ZoneId sessionZone) public void testJulianGregorianDate() { // Oracle TO_DATE function returns +10 days during julian and gregorian calendar switch - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_julian_dt", "(ts date)")) { + try (TestTable table = newTrinoTable("test_julian_dt", "(ts date)")) { assertUpdate(format("INSERT INTO %s VALUES (DATE '1582-10-05')", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES TIMESTAMP '1582-10-15 00:00:00'"); } @@ -701,7 +701,7 @@ public void testJulianGregorianDate() @Test public void testUnsupportedDate() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_unsupported_dt", "(ts date)")) { + try (TestTable table = newTrinoTable("test_unsupported_dt", "(ts date)")) { assertQueryFails( format("INSERT INTO %s VALUES (DATE '-4713-12-31')", table.getName()), """ @@ -960,7 +960,7 @@ public void testTimestampAllPrecisionsOnOracle() public void testJulianGregorianTimestamp() { // Oracle TO_DATE function returns +10 days during julian and gregorian calendar switch - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_julian_ts", "(ts date)")) { + try (TestTable table = newTrinoTable("test_julian_ts", "(ts date)")) { assertUpdate(format("INSERT INTO %s VALUES (timestamp '1582-10-05')", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES TIMESTAMP '1582-10-15 00:00:00'"); } @@ -969,7 +969,7 @@ public void testJulianGregorianTimestamp() @Test public void testUnsupportedTimestamp() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_unsupported_ts", "(ts timestamp)")) { + try (TestTable table = newTrinoTable("test_unsupported_ts", "(ts timestamp)")) { assertQueryFails( format("INSERT INTO %s VALUES (TIMESTAMP '-4713-12-31 00:00:00.000')", table.getName()), """ 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 b85473807bf6..435a0fc2f07f 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 @@ -198,8 +198,7 @@ public void testCharVarcharComparison() { // test overridden because super uses all-space char values (' ') that are null-out by Oracle - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_char_varchar", "(k, v) AS VALUES" + " (-1, CAST(NULL AS char(3))), " + @@ -222,8 +221,7 @@ public void testVarcharCharComparison() { // test overridden because Oracle nulls-out '' varchar value, impacting results - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_varchar_char", "(k, v) AS VALUES" + " (-1, CAST(NULL AS varchar(3))), " + @@ -500,8 +498,8 @@ private void predicatePushdownTest(String oracleType, String oracleLiteral, Stri @Test public void testJoinPushdownWithImplicitCast() { - try (TestTable leftTable = new TestTable(getQueryRunner()::execute, "left_table", "(id int, varchar_50 varchar(50))", ImmutableList.of("(1, 'India')", "(2, 'Poland')")); - TestTable rightTable = new TestTable(getQueryRunner()::execute, "right_table_", "(varchar_100 varchar(100), varchar_unbounded varchar)", ImmutableList.of("('India', 'Japan')", "('France', 'Poland')"))) { + try (TestTable leftTable = newTrinoTable("left_table", "(id int, varchar_50 varchar(50))", ImmutableList.of("(1, 'India')", "(2, 'Poland')")); + TestTable rightTable = newTrinoTable("right_table_", "(varchar_100 varchar(100), varchar_unbounded varchar)", ImmutableList.of("('India', 'Japan')", "('France', 'Poland')"))) { String leftTableName = leftTable.getName(); String rightTableName = rightTable.getName(); Session session = joinPushdownEnabled(getSession()); 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 6836fcce8859..490dff0c94eb 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 @@ -26,9 +26,6 @@ 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 BaseOracleFailureRecoveryTest extends BaseJdbcFailureRecoveryTest { @@ -59,14 +56,6 @@ protected QueryRunner createQueryRunner( .build(); } - @Test - @Override - protected void testUpdateWithSubquery() - { - assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - abort("skipped"); - } - @Test @Override protected void testUpdate() diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index 3d386230d167..a1b94adf0727 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 5689cb9b2438..bb40dac86d77 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 @@ -24,14 +24,13 @@ import java.security.Principal; import java.util.concurrent.TimeUnit; -import static com.google.common.base.Strings.emptyToNull; import static com.google.common.net.MediaType.ANY_TEXT_TYPE; import static io.airlift.http.client.HttpStatus.OK; import static io.airlift.http.client.testing.TestingResponse.mockResponse; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; 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; public class TestSalesforceBasicAuthenticator @@ -182,15 +181,9 @@ public void createAuthenticatedPrincipalRealSuccess() abort("Skipping real tests."); } - String org = System.getenv("SALESFORCE_TEST_ORG"); - if (emptyToNull(org) == null) { - fail("Must set SALESFORCE_TEST_ORG environment variable."); - } - String username = System.getenv("SALESFORCE_TEST_USERNAME"); - String password = System.getenv("SALESFORCE_TEST_PASSWORD"); - if (emptyToNull(username) == null || emptyToNull(password) == null) { - fail("Must set SALESFORCE_TEST_USERNAME and SALESFORCE_TEST_PASSWORD environment variables."); - } + String org = requireEnv("SALESFORCE_TEST_ORG"); + String username = requireEnv("SALESFORCE_TEST_USERNAME"); + String password = requireEnv("SALESFORCE_TEST_PASSWORD"); SalesforceConfig config = new SalesforceConfig() .setAllowedOrganizations(org); @@ -213,11 +206,8 @@ public void createAuthenticatedPrincipalRealWrongOrg() abort("Skipping real tests."); } - String username = System.getenv("SALESFORCE_TEST_USERNAME"); - String password = System.getenv("SALESFORCE_TEST_PASSWORD"); - if (emptyToNull(username) == null || emptyToNull(password) == null) { - fail("Must set SALESFORCE_TEST_USERNAME and SALESFORCE_TEST_PASSWORD environment variables."); - } + String username = requireEnv("SALESFORCE_TEST_USERNAME"); + String password = requireEnv("SALESFORCE_TEST_PASSWORD"); String org = "NotMyOrg"; SalesforceConfig config = new SalesforceConfig() @@ -240,11 +230,8 @@ public void createAuthenticatedPrincipalRealAllOrgs() abort("Skipping real tests."); } - String username = System.getenv("SALESFORCE_TEST_USERNAME"); - String password = System.getenv("SALESFORCE_TEST_PASSWORD"); - if (emptyToNull(username) == null || emptyToNull(password) == null) { - fail("Must set SALESFORCE_TEST_USERNAME and SALESFORCE_TEST_PASSWORD environment variables."); - } + String username = requireEnv("SALESFORCE_TEST_USERNAME"); + String password = requireEnv("SALESFORCE_TEST_PASSWORD"); SalesforceConfig config = new SalesforceConfig() .setAllowedOrganizations("all"); @@ -268,15 +255,8 @@ public void createAuthenticatedPrincipalRealBadPassword() abort("Skipping real tests."); } - String org = System.getenv("SALESFORCE_TEST_ORG"); - if (emptyToNull(org) == null) { - fail("Must set SALESFORCE_TEST_ORG environment variable."); - } - String username = System.getenv("SALESFORCE_TEST_USERNAME"); - String password = System.getenv("SALESFORCE_TEST_PASSWORD"); - if (emptyToNull(username) == null || emptyToNull(password) == null) { - fail("Must set SALESFORCE_TEST_USERNAME and SALESFORCE_TEST_PASSWORD environment variables."); - } + String org = requireEnv("SALESFORCE_TEST_ORG"); + String username = requireEnv("SALESFORCE_TEST_USERNAME"); SalesforceConfig config = new SalesforceConfig() .setAllowedOrganizations(org); diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index 3b9fb9f5617d..dd25843aed77 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 4797960ab08d..b34a36913e9f 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 @@ -335,8 +335,7 @@ public void testCharVarcharComparison() { // test overridden because super uses all-space char values (' ') that are null-out by Phoenix - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_char_varchar", "(k, v) AS VALUES" + " (-1, CAST(NULL AS char(3))), " + @@ -359,8 +358,7 @@ public void testVarcharCharComparison() { // test overridden because Phoenix nulls-out '' varchar value, impacting results - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_varchar_char", "(k, v) AS VALUES" + " (-1, CAST(NULL AS varchar(3))), " + @@ -409,7 +407,7 @@ public void testCountDistinctWithStringTypes() .collect(toImmutableList()); String tableName = "count_distinct_strings" + randomNameSuffix(); - try (TestTable testTable = new TestTable(getQueryRunner()::execute, tableName, "(id int, t_char CHAR(5), t_varchar VARCHAR(5)) WITH (ROWKEYS='id')", rows)) { + try (TestTable testTable = newTrinoTable(tableName, "(id int, t_char CHAR(5), t_varchar VARCHAR(5)) WITH (ROWKEYS='id')", rows)) { assertQuery("SELECT count(DISTINCT t_varchar) FROM " + testTable.getName(), "VALUES 6"); assertQuery("SELECT count(DISTINCT t_char) FROM " + testTable.getName(), "VALUES 6"); assertQuery("SELECT count(DISTINCT t_char), count(DISTINCT t_varchar) FROM " + testTable.getName(), "VALUES (6, 6)"); 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 19b9a0879327..fc59390ded20 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 @@ -715,7 +715,7 @@ public void testArray() public void testArrayNulls() { // Verify only SELECT instead of using SqlDataTypeTest because array comparison not supported for arrays with null elements - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_array_nulls", "(c1 ARRAY(boolean), c2 ARRAY(varchar), c3 ARRAY(varchar))", ImmutableList.of("(NULL, ARRAY[NULL], ARRAY['foo', NULL, 'bar', NULL])"))) { + try (TestTable table = newTrinoTable("test_array_nulls", "(c1 ARRAY(boolean), c2 ARRAY(varchar), c3 ARRAY(varchar))", ImmutableList.of("(NULL, ARRAY[NULL], ARRAY['foo', NULL, 'bar', NULL])"))) { assertThat(query("SELECT c1 FROM " + table.getName())).matches("VALUES CAST(NULL AS ARRAY(boolean))"); assertThat(query("SELECT c2 FROM " + table.getName())).matches("VALUES CAST(ARRAY[NULL] AS ARRAY(varchar))"); assertThat(query("SELECT c3 FROM " + table.getName())).matches("VALUES CAST(ARRAY['foo', NULL, 'bar', NULL] AS ARRAY(varchar))"); diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index e745a06616aa..baa74b10aca9 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index 465cf9c92ba1..d7105256f301 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 7cc262b79ea9..68bd81cb23ba 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 @@ -1509,11 +1509,15 @@ private static SliceReadFunction arrayAsJsonReadFunction(ConnectorSession sessio type.writeObject(builder, block); Object value = type.getObjectValue(session, builder.build(), 0); + if (!(value instanceof List list)) { + throw new TrinoException(JDBC_ERROR, "Unexpected JSON object value for " + type.getDisplayName() + " expected List, got " + value.getClass().getSimpleName()); + } + try { - return toJsonValue(value); + return toJsonValue(list); } catch (IOException e) { - throw new TrinoException(JDBC_ERROR, "Conversion to JSON failed for " + type.getDisplayName(), e); + throw new TrinoException(JDBC_ERROR, "Conversion to JSON failed for " + type.getDisplayName(), e); } }; } 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 c5f49addd7db..53b2211c17d4 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 @@ -74,7 +74,7 @@ protected void testDeleteWithSubquery() @Override protected void testUpdateWithSubquery() { - assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); + assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Non-transactional MERGE is disabled"); abort("skipped"); } 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 4e39f890ab86..03e3389798eb 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 @@ -194,8 +194,7 @@ public void testTimestampPrecisionOnCreateTable() private void testTimestampPrecisionOnCreateTable(String inputType, String expectedType) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_coercion_show_create_table", format("(a %s)", inputType))) { assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(expectedType); @@ -234,8 +233,7 @@ public void testTimestampPrecisionOnCreateTableAsSelect() private void testTimestampPrecisionOnCreateTableAsSelect(String inputType, String tableType, String tableValue) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_coercion_show_create_table", format("AS SELECT %s a", inputType))) { assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(tableType); @@ -277,8 +275,7 @@ public void testTimestampPrecisionOnCreateTableAsSelectWithNoData() private void testTimestampPrecisionOnCreateTableAsSelectWithNoData(String inputType, String tableType) { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_coercion_show_create_table", format("AS SELECT %s a WITH NO DATA", inputType))) { assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(tableType); @@ -833,8 +830,7 @@ public void testLikePredicatePushdown() assertThat(query("SELECT nationkey FROM nation WHERE name LIKE '%A%'")) .isFullyPushedDown(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_like_predicate_pushdown", "(id integer, a_varchar varchar(1))", List.of( @@ -856,8 +852,7 @@ public void testLikeWithEscapePredicatePushdown() assertThat(query("SELECT nationkey FROM nation WHERE name LIKE '%A%' ESCAPE '\\'")) .isFullyPushedDown(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_like_with_escape_predicate_pushdown", "(id integer, a_varchar varchar(4))", List.of( @@ -878,8 +873,7 @@ 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 regionkey = 4")).isFullyPushedDown(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_is_null_predicate_pushdown", "(a_int integer, a_varchar varchar(1))", List.of( @@ -896,8 +890,7 @@ 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, + try (TestTable table = newTrinoTable( "test_is_not_null_predicate_pushdown", "(a_int integer, a_varchar varchar(1))", List.of( @@ -935,8 +928,7 @@ public void testNotExpressionPushdown() { assertThat(query("SELECT nationkey FROM nation WHERE NOT(name LIKE '%A%' ESCAPE '\\')")).isFullyPushedDown(); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_is_not_predicate_pushdown", "(a_int integer, a_varchar varchar(2))", List.of( @@ -952,8 +944,7 @@ public void testNotExpressionPushdown() @Test public void testInPredicatePushdown() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_in_predicate_pushdown", "(id varchar(1), id2 varchar(1))", List.of( @@ -1095,8 +1086,7 @@ public void testTimestampColumnAndTimestampWithTimeZoneConstant() @Test public void testReverseFunctionProjectionPushDown() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_reverse_pushdown_for_project", "(id BIGINT, varchar_col VARCHAR)", ImmutableList.of("1, 'abc'", "2, null"))) { @@ -1151,8 +1141,7 @@ public void testReverseFunctionProjectionPushDown() @Test public void testPartialProjectionPushDown() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_partial_projection_pushdown", "(id BIGINT, cola VARCHAR, colb VARCHAR)", ImmutableList.of("1, 'abc', 'def'"))) { @@ -1202,8 +1191,7 @@ public void testPartialProjectionPushDown() @Test public void testProjectionsNotPushDownWhenFilterAppliedOnProjectedColumn() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_projection_push_down_with_filter", "(id BIGINT, cola VARCHAR, colb VARCHAR)", ImmutableList.of("1, 'abc', 'def'"))) { diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index f8e11f295707..5cf75975de3e 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-ranger/pom.xml b/plugin/trino-ranger/pom.xml index 2f60db528737..52de64f55b64 100644 --- a/plugin/trino-ranger/pom.xml +++ b/plugin/trino-ranger/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index 7722bb80f16c..c7e119b77ac1 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index a18cfb70890d..8ebcc07a11a8 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -20,6 +20,11 @@ 2.1.0.30 + + com.fasterxml.jackson.core + jackson-databind + + com.google.guava guava @@ -30,21 +35,61 @@ guice + + io.airlift + bootstrap + + io.airlift configuration + + io.airlift + json + + + + io.airlift + log + + + + io.airlift + units + + io.trino trino-base-jdbc + + io.trino + trino-filesystem + + + + io.trino + trino-filesystem-s3 + + io.trino trino-matching + + io.trino + trino-memory-context + + + + io.trino + trino-parquet + + io.trino trino-plugin-toolkit @@ -55,6 +100,16 @@ jakarta.validation-api + + joda-time + joda-time + + + + org.apache.parquet + parquet-column + + org.jdbi jdbi3-core @@ -116,19 +171,31 @@ io.airlift - log + log-manager runtime - io.airlift - log-manager + software.amazon.awssdk + auth runtime - io.airlift - units + software.amazon.awssdk + aws-core + runtime + + + + software.amazon.awssdk + regions + runtime + + + + software.amazon.awssdk + s3 runtime @@ -236,9 +303,11 @@ **/TestRedshiftAutomaticJoinPushdown.java **/TestRedshiftCastPushdown.java **/TestRedshiftConnectorTest.java + **/TestRedshiftUnload.java **/TestRedshiftConnectorSmokeTest.java **/TestRedshiftTableStatisticsReader.java **/TestRedshiftTypeMapping.java + **/TestRedshiftUnloadTypeMapping.java **/Test*FailureRecoveryTest.java **/Test*FailureRecoverySmokeTest.java @@ -265,6 +334,8 @@ **/TestRedshiftCastPushdown.java **/TestRedshiftConnectorSmokeTest.java + **/TestRedshiftUnloadTypeMapping.java + **/TestRedshiftUnload.java diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClientModule.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClientModule.java index d0e590c9a52d..0c6e01999ab1 100644 --- a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClientModule.java +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClientModule.java @@ -15,29 +15,44 @@ import com.amazon.redshift.Driver; import com.google.inject.Binder; +import com.google.inject.Key; import com.google.inject.Provides; +import com.google.inject.Scopes; import com.google.inject.Singleton; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.opentelemetry.api.OpenTelemetry; +import io.trino.filesystem.s3.S3FileSystemModule; +import io.trino.plugin.base.metrics.FileFormatDataSourceStats; import io.trino.plugin.jdbc.BaseJdbcConfig; import io.trino.plugin.jdbc.ConnectionFactory; import io.trino.plugin.jdbc.DecimalModule; import io.trino.plugin.jdbc.DriverConnectionFactory; import io.trino.plugin.jdbc.ForBaseJdbc; +import io.trino.plugin.jdbc.ForJdbcDynamicFiltering; import io.trino.plugin.jdbc.JdbcClient; +import io.trino.plugin.jdbc.JdbcConnector; import io.trino.plugin.jdbc.JdbcJoinPushdownSupportModule; import io.trino.plugin.jdbc.JdbcMetadataConfig; +import io.trino.plugin.jdbc.JdbcQueryEventListener; +import io.trino.plugin.jdbc.JdbcRecordSetProvider; +import io.trino.plugin.jdbc.JdbcSplitManager; import io.trino.plugin.jdbc.JdbcStatisticsConfig; import io.trino.plugin.jdbc.RemoteQueryCancellationModule; import io.trino.plugin.jdbc.credential.CredentialProvider; import io.trino.plugin.jdbc.ptf.Query; +import io.trino.spi.connector.Connector; +import io.trino.spi.connector.ConnectorRecordSetProvider; +import io.trino.spi.connector.ConnectorSplitManager; import io.trino.spi.function.table.ConnectorTableFunction; import java.util.Properties; import static com.google.inject.Scopes.SINGLETON; import static com.google.inject.multibindings.Multibinder.newSetBinder; +import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; +import static io.airlift.configuration.ConditionalModule.conditionalModule; import static io.airlift.configuration.ConfigBinder.configBinder; +import static io.trino.plugin.jdbc.JdbcModule.bindSessionPropertiesProvider; public class RedshiftClientModule extends AbstractConfigurationAwareModule @@ -50,10 +65,28 @@ public void setup(Binder binder) configBinder(binder).bindConfigDefaults(JdbcMetadataConfig.class, config -> config.setBulkListColumns(true)); newSetBinder(binder, ConnectorTableFunction.class).addBinding().toProvider(Query.class).in(SINGLETON); configBinder(binder).bindConfig(JdbcStatisticsConfig.class); + bindSessionPropertiesProvider(binder, RedshiftSessionProperties.class); install(new DecimalModule()); install(new JdbcJoinPushdownSupportModule()); install(new RemoteQueryCancellationModule()); + binder.bind(ConnectorRecordSetProvider.class).to(JdbcRecordSetProvider.class).in(Scopes.SINGLETON); + + install(conditionalModule( + RedshiftConfig.class, + config -> config.getUnloadLocation().isPresent(), + unloadBinder -> { + install(new S3FileSystemModule()); + unloadBinder.bind(JdbcSplitManager.class).in(Scopes.SINGLETON); + unloadBinder.bind(Connector.class).to(RedshiftUnloadConnector.class).in(Scopes.SINGLETON); + unloadBinder.bind(FileFormatDataSourceStats.class).in(Scopes.SINGLETON); + + newSetBinder(unloadBinder, JdbcQueryEventListener.class).addBinding().to(RedshiftUnloadJdbcQueryEventListener.class).in(Scopes.SINGLETON); + + newOptionalBinder(unloadBinder, Key.get(ConnectorSplitManager.class, ForJdbcDynamicFiltering.class)) + .setBinding().to(RedshiftSplitManager.class).in(SINGLETON); + }, + jdbcBinder -> jdbcBinder.bind(Connector.class).to(JdbcConnector.class).in(Scopes.SINGLETON))); } @Singleton diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftConfig.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftConfig.java index 370f31e7dc8b..0d8a193ff5f4 100644 --- a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftConfig.java +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftConfig.java @@ -17,6 +17,7 @@ import io.airlift.configuration.ConfigDescription; import io.airlift.configuration.DefunctConfig; import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Pattern; import java.util.Optional; @@ -27,6 +28,8 @@ public class RedshiftConfig { private Integer fetchSize; + private String unloadLocation; + private String unloadIamRole; public Optional<@Min(0) Integer> getFetchSize() { @@ -40,4 +43,30 @@ public RedshiftConfig setFetchSize(Integer fetchSize) this.fetchSize = fetchSize; return this; } + + public Optional<@Pattern(regexp = "^s3://[^/]+(/[^/]+)?$", message = "Path shouldn't end with trailing slash") String> getUnloadLocation() + { + return Optional.ofNullable(unloadLocation); + } + + @Config("redshift.unload-location") + @ConfigDescription("A writeable location in Amazon S3, to be used for unloading Redshift query results") + public RedshiftConfig setUnloadLocation(String unloadLocation) + { + this.unloadLocation = unloadLocation; + return this; + } + + public Optional getUnloadIamRole() + { + return Optional.ofNullable(unloadIamRole); + } + + @Config("redshift.unload-iam-role") + @ConfigDescription("Fully specified ARN of the IAM Role attached to the Redshift cluster and having access to S3") + public RedshiftConfig setUnloadIamRole(String unloadIamRole) + { + this.unloadIamRole = unloadIamRole; + return this; + } } diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftConnectorFactory.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftConnectorFactory.java new file mode 100644 index 000000000000..6a2a5e2e563c --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftConnectorFactory.java @@ -0,0 +1,68 @@ +/* + * 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.redshift; + +import com.google.inject.Injector; +import io.airlift.bootstrap.Bootstrap; +import io.opentelemetry.api.OpenTelemetry; +import io.trino.plugin.jdbc.ExtraCredentialsBasedIdentityCacheMappingModule; +import io.trino.plugin.jdbc.JdbcModule; +import io.trino.plugin.jdbc.credential.CredentialProviderModule; +import io.trino.spi.NodeManager; +import io.trino.spi.VersionEmbedder; +import io.trino.spi.catalog.CatalogName; +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.Map; + +import static io.trino.plugin.base.Versions.checkStrictSpiVersionMatch; +import static java.util.Objects.requireNonNull; + +public class RedshiftConnectorFactory + implements ConnectorFactory +{ + @Override + public String getName() + { + return "redshift"; + } + + @Override + public Connector create(String catalogName, Map requiredConfig, ConnectorContext context) + { + requireNonNull(requiredConfig, "requiredConfig is null"); + checkStrictSpiVersionMatch(context, this); + + Bootstrap app = new Bootstrap( + binder -> binder.bind(TypeManager.class).toInstance(context.getTypeManager()), + binder -> binder.bind(NodeManager.class).toInstance(context.getNodeManager()), + binder -> binder.bind(VersionEmbedder.class).toInstance(context.getVersionEmbedder()), + binder -> binder.bind(OpenTelemetry.class).toInstance(context.getOpenTelemetry()), + binder -> binder.bind(CatalogName.class).toInstance(new CatalogName(catalogName)), + new JdbcModule(), + new CredentialProviderModule(), + new ExtraCredentialsBasedIdentityCacheMappingModule(), + new RedshiftClientModule()); + + Injector injector = app + .doNotInitializeLogging() + .setRequiredConfigurationProperties(requiredConfig) + .initialize(); + + return injector.getInstance(Connector.class); + } +} diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftErrorCode.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftErrorCode.java index e09279270270..2d9fa999a2ac 100644 --- a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftErrorCode.java +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftErrorCode.java @@ -23,6 +23,10 @@ public enum RedshiftErrorCode implements ErrorCodeSupplier { REDSHIFT_INVALID_TYPE(0, EXTERNAL), + REDSHIFT_PARQUET_BAD_DATA(1, EXTERNAL), + REDSHIFT_PARQUET_CURSOR_ERROR(2, EXTERNAL), + REDSHIFT_FILESYSTEM_ERROR(3, EXTERNAL), + REDSHIFT_S3_CROSS_REGION_UNSUPPORTED(4, EXTERNAL), /**/; private final ErrorCode errorCode; diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftPageSourceProvider.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftPageSourceProvider.java new file mode 100644 index 000000000000..ff14082a2878 --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftPageSourceProvider.java @@ -0,0 +1,145 @@ +/* + * 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.redshift; + +import com.google.common.collect.ImmutableList; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.filesystem.TrinoInputFile; +import io.trino.parquet.Column; +import io.trino.parquet.ParquetReaderOptions; +import io.trino.parquet.metadata.BlockMetadata; +import io.trino.parquet.metadata.ParquetMetadata; +import io.trino.parquet.reader.MetadataReader; +import io.trino.parquet.reader.ParquetReader; +import io.trino.parquet.reader.RowGroupInfo; +import io.trino.plugin.base.metrics.FileFormatDataSourceStats; +import io.trino.plugin.jdbc.JdbcColumnHandle; +import io.trino.plugin.jdbc.JdbcSplit; +import io.trino.spi.TrinoException; +import io.trino.spi.connector.ColumnHandle; +import io.trino.spi.connector.ConnectorPageSource; +import io.trino.spi.connector.ConnectorPageSourceProvider; +import io.trino.spi.connector.ConnectorRecordSetProvider; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorSplit; +import io.trino.spi.connector.ConnectorTableHandle; +import io.trino.spi.connector.ConnectorTransactionHandle; +import io.trino.spi.connector.DynamicFilter; +import io.trino.spi.connector.RecordPageSource; +import org.apache.parquet.column.ColumnDescriptor; +import org.apache.parquet.io.MessageColumnIO; +import org.apache.parquet.schema.MessageType; +import org.joda.time.DateTimeZone; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; +import static io.trino.parquet.ParquetTypeUtils.constructField; +import static io.trino.parquet.ParquetTypeUtils.getColumnIO; +import static io.trino.parquet.ParquetTypeUtils.getDescriptors; +import static io.trino.parquet.ParquetTypeUtils.lookupColumnByName; +import static io.trino.parquet.metadata.PrunedBlockMetadata.createPrunedColumnsMetadata; +import static io.trino.plugin.redshift.RedshiftErrorCode.REDSHIFT_PARQUET_CURSOR_ERROR; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +public class RedshiftPageSourceProvider + implements ConnectorPageSourceProvider +{ + private final ConnectorRecordSetProvider recordSetProvider; + private final TrinoFileSystemFactory fileSystemFactory; + private final FileFormatDataSourceStats fileFormatDataSourceStats; + + public RedshiftPageSourceProvider(ConnectorRecordSetProvider recordSetProvider, TrinoFileSystemFactory fileSystemFactory, FileFormatDataSourceStats fileFormatDataSourceStats) + { + this.recordSetProvider = requireNonNull(recordSetProvider, "recordSetProvider is null"); + this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); + this.fileFormatDataSourceStats = requireNonNull(fileFormatDataSourceStats, "fileFormatDataSourceStats is null"); + } + + @Override + public ConnectorPageSource createPageSource( + ConnectorTransactionHandle transaction, + ConnectorSession session, + ConnectorSplit split, + ConnectorTableHandle table, + List columns, + DynamicFilter dynamicFilter) + { + if (split instanceof JdbcSplit) { + return new RecordPageSource(recordSetProvider.getRecordSet(transaction, session, split, table, columns)); + } + + RedshiftUnloadSplit redshiftUnloadSplit = ((RedshiftUnloadSplit) split); + String path = redshiftUnloadSplit.path(); + Location location = Location.of(path); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + TrinoInputFile inputFile = fileSystem.newInputFile(location, redshiftUnloadSplit.length()); + ParquetReader parquetReader; + try { + parquetReader = parquetReader(inputFile, columns); + } + catch (IOException e) { + throw new TrinoException(REDSHIFT_PARQUET_CURSOR_ERROR, format("Failed to open Parquet file: %s", path), e); + } + return new RedshiftParquetPageSource(parquetReader); + } + + private ParquetReader parquetReader(TrinoInputFile inputFile, List columns) + throws IOException + { + ParquetReaderOptions options = new ParquetReaderOptions(); + TrinoParquetDataSource dataSource = new TrinoParquetDataSource(inputFile, options, fileFormatDataSourceStats); + ParquetMetadata parquetMetadata = MetadataReader.readFooter(dataSource, Optional.empty()); + MessageType fileSchema = parquetMetadata.getFileMetaData().getSchema(); + MessageColumnIO messageColumn = getColumnIO(fileSchema, fileSchema); + Map, ColumnDescriptor> descriptorsByPath = getDescriptors(fileSchema, fileSchema); + DateTimeZone timeZone = DateTimeZone.UTC; + List fields = fields(columns, messageColumn); + long nextStart = 0; + ImmutableList.Builder rowGroupInfoBuilder = ImmutableList.builder(); + for (BlockMetadata block : parquetMetadata.getBlocks()) { + rowGroupInfoBuilder.add(new RowGroupInfo(createPrunedColumnsMetadata(block, dataSource.getId(), descriptorsByPath), nextStart, Optional.empty())); + nextStart += block.rowCount(); + } + return new ParquetReader( + Optional.ofNullable(parquetMetadata.getFileMetaData().getCreatedBy()), + fields, + rowGroupInfoBuilder.build(), + dataSource, + timeZone, + newSimpleAggregatedMemoryContext(), + options, + RedshiftParquetPageSource::handleException, + Optional.empty(), + Optional.empty()); + } + + private static List fields(List columns, MessageColumnIO messageColumn) + { + ImmutableList.Builder parquetColumnFieldsBuilder = ImmutableList.builder(); + for (ColumnHandle column : columns) { + JdbcColumnHandle jdbcColumn = (JdbcColumnHandle) column; + constructField(jdbcColumn.getColumnType(), lookupColumnByName(messageColumn, jdbcColumn.getColumnName())) + .ifPresent(field -> parquetColumnFieldsBuilder.add(new Column(jdbcColumn.getColumnName(), field))); + } + + return parquetColumnFieldsBuilder.build(); + } +} diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftParquetPageSource.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftParquetPageSource.java new file mode 100644 index 000000000000..3371f28c6e98 --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftParquetPageSource.java @@ -0,0 +1,127 @@ +/* + * 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.redshift; + +import io.trino.parquet.ParquetCorruptionException; +import io.trino.parquet.reader.ParquetReader; +import io.trino.spi.Page; +import io.trino.spi.TrinoException; +import io.trino.spi.connector.ConnectorPageSource; +import io.trino.spi.metrics.Metrics; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.OptionalLong; + +import static io.trino.plugin.base.util.Closables.closeAllSuppress; +import static io.trino.plugin.redshift.RedshiftErrorCode.REDSHIFT_PARQUET_BAD_DATA; +import static io.trino.plugin.redshift.RedshiftErrorCode.REDSHIFT_PARQUET_CURSOR_ERROR; +import static java.util.Objects.requireNonNull; + +public class RedshiftParquetPageSource + implements ConnectorPageSource +{ + private final ParquetReader parquetReader; + private boolean closed; + private long completedPositions; + + public RedshiftParquetPageSource(ParquetReader parquetReader) + { + this.parquetReader = requireNonNull(parquetReader, "parquetReader is null"); + } + + @Override + public long getCompletedBytes() + { + return parquetReader.getDataSource().getReadBytes(); + } + + @Override + public OptionalLong getCompletedPositions() + { + return OptionalLong.of(completedPositions); + } + + @Override + public long getReadTimeNanos() + { + return parquetReader.getDataSource().getReadTimeNanos(); + } + + @Override + public boolean isFinished() + { + return closed; + } + + @Override + public Page getNextPage() + { + Page page; + try { + page = parquetReader.nextPage(); + } + catch (IOException | RuntimeException e) { + closeAllSuppress(e, this); + throw handleException(e); + } + + if (closed || page == null) { + close(); + return null; + } + + completedPositions += page.getPositionCount(); + return page; + } + + @Override + public long getMemoryUsage() + { + return parquetReader.getMemoryContext().getBytes(); + } + + @Override + public void close() + { + if (closed) { + return; + } + closed = true; + + try { + parquetReader.close(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public Metrics getMetrics() + { + return parquetReader.getMetrics(); + } + + static TrinoException handleException(Exception exception) + { + if (exception instanceof TrinoException) { + return (TrinoException) exception; + } + if (exception instanceof ParquetCorruptionException) { + return new TrinoException(REDSHIFT_PARQUET_BAD_DATA, exception); + } + return new TrinoException(REDSHIFT_PARQUET_CURSOR_ERROR, exception.getMessage(), exception); + } +} diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftPlugin.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftPlugin.java index 25309d0e6484..5757afe7742a 100644 --- a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftPlugin.java +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftPlugin.java @@ -13,13 +13,16 @@ */ package io.trino.plugin.redshift; -import io.trino.plugin.jdbc.JdbcPlugin; +import com.google.common.collect.ImmutableList; +import io.trino.spi.Plugin; +import io.trino.spi.connector.ConnectorFactory; public class RedshiftPlugin - extends JdbcPlugin + implements Plugin { - public RedshiftPlugin() + @Override + public Iterable getConnectorFactories() { - super("redshift", RedshiftClientModule::new); + return ImmutableList.of(new RedshiftConnectorFactory()); } } diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftSessionProperties.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftSessionProperties.java new file mode 100644 index 000000000000..48c7c75a2290 --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftSessionProperties.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.redshift; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; +import io.trino.plugin.base.session.SessionPropertiesProvider; +import io.trino.spi.TrinoException; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.session.PropertyMetadata; + +import java.util.List; + +import static io.trino.spi.StandardErrorCode.INVALID_SESSION_PROPERTY; +import static io.trino.spi.session.PropertyMetadata.booleanProperty; + +public class RedshiftSessionProperties + implements SessionPropertiesProvider +{ + private static final String UNLOAD_ENABLED = "unload_enabled"; + + private final List> sessionProperties; + + @Inject + public RedshiftSessionProperties(RedshiftConfig config) + { + sessionProperties = ImmutableList.>builder() + .add(booleanProperty( + UNLOAD_ENABLED, + "Use UNLOAD for reading query results", + config.getUnloadLocation().isPresent(), + value -> { + if (value && config.getUnloadLocation().isEmpty()) { + throw new TrinoException(INVALID_SESSION_PROPERTY, "Cannot use UNLOAD when unload location is not configured"); + } + }, + false)) + .build(); + } + + @Override + public List> getSessionProperties() + { + return sessionProperties; + } + + public static boolean isUnloadEnabled(ConnectorSession session) + { + return session.getProperty(UNLOAD_ENABLED, Boolean.class); + } +} diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftSplitManager.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftSplitManager.java new file mode 100644 index 000000000000..5851bfb3c151 --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftSplitManager.java @@ -0,0 +1,157 @@ +/* + * 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.redshift; + +import com.google.inject.Inject; +import io.airlift.log.Logger; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.filesystem.s3.FileSystemS3; +import io.trino.plugin.jdbc.ForRecordCursor; +import io.trino.plugin.jdbc.JdbcClient; +import io.trino.plugin.jdbc.JdbcColumnHandle; +import io.trino.plugin.jdbc.JdbcProcedureHandle; +import io.trino.plugin.jdbc.JdbcSplit; +import io.trino.plugin.jdbc.JdbcSplitManager; +import io.trino.plugin.jdbc.JdbcTableHandle; +import io.trino.plugin.jdbc.QueryBuilder; +import io.trino.plugin.jdbc.logging.RemoteQueryModifier; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorSplitManager; +import io.trino.spi.connector.ConnectorSplitSource; +import io.trino.spi.connector.ConnectorTableHandle; +import io.trino.spi.connector.ConnectorTransactionHandle; +import io.trino.spi.connector.Constraint; +import io.trino.spi.connector.DynamicFilter; +import io.trino.spi.connector.FixedSplitSource; +import io.trino.spi.type.DecimalType; +import io.trino.spi.type.TimeType; +import io.trino.spi.type.VarbinaryType; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutorService; + +import static io.trino.plugin.jdbc.JdbcDynamicFilteringSessionProperties.dynamicFilteringEnabled; +import static io.trino.plugin.redshift.RedshiftSessionProperties.isUnloadEnabled; +import static java.util.Objects.requireNonNull; + +public class RedshiftSplitManager + implements ConnectorSplitManager +{ + private static final Logger log = Logger.get(RedshiftSplitManager.class); + + private final JdbcClient jdbcClient; + private final QueryBuilder queryBuilder; + private final RemoteQueryModifier queryModifier; + private final JdbcSplitManager jdbcSplitManager; + private final Optional unloadLocation; + private final Optional unloadAuthorization; + private final ExecutorService executor; + private final TrinoFileSystemFactory fileSystemFactory; + + @Inject + public RedshiftSplitManager( + JdbcClient jdbcClient, + QueryBuilder queryBuilder, + RemoteQueryModifier queryModifier, + JdbcSplitManager jdbcSplitManager, + RedshiftConfig redshiftConfig, + @FileSystemS3 TrinoFileSystemFactory fileSystemFactory, + @ForRecordCursor ExecutorService executor) + { + this.jdbcClient = requireNonNull(jdbcClient, "jdbcClient is null"); + this.queryBuilder = requireNonNull(queryBuilder, "queryBuilder is null"); + this.queryModifier = requireNonNull(queryModifier, "queryModifier is null"); + this.jdbcSplitManager = requireNonNull(jdbcSplitManager, "jdbcSplitManager is null"); + this.unloadLocation = redshiftConfig.getUnloadLocation(); + this.unloadAuthorization = redshiftConfig.getUnloadIamRole(); + this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); + this.executor = requireNonNull(executor, "executor is null"); + } + + @Override + public ConnectorSplitSource getSplits(ConnectorTransactionHandle transaction, ConnectorSession session, ConnectorTableHandle table, DynamicFilter dynamicFilter, Constraint constraint) + { + if (table instanceof JdbcProcedureHandle) { + return jdbcSplitManager.getSplits(transaction, session, table, dynamicFilter, constraint); + } + ConnectorSplitSource fallbackSplitSource = new FixedSplitSource(new JdbcSplit(Optional.empty())); + if (!isUnloadEnabled(session)) { + return fallbackSplitSource; + } + JdbcTableHandle jdbcTable = (JdbcTableHandle) table; + JdbcTableHandle jdbcTableHandle = dynamicFilteringEnabled(session) ? jdbcTable.intersectedWithConstraint(dynamicFilter.getCurrentPredicate()) : jdbcTable; + List columns = jdbcTableHandle.getColumns() + .orElseGet(() -> jdbcClient.getColumns( + session, + jdbcTableHandle.getRequiredNamedRelation().getSchemaTableName(), + jdbcTableHandle.getRequiredNamedRelation().getRemoteTableName())); + + if (!isUnloadSupported(jdbcTable, columns)) { + log.debug("Unsupported query shape detected. Falling back to using JDBC"); + return fallbackSplitSource; + } + return new RedshiftUnloadSplitSource( + executor, + session, + jdbcClient, + jdbcTableHandle, + columns, + queryBuilder, + queryModifier, + unloadLocation.orElseThrow(), + unloadAuthorization, + fileSystemFactory.create(session)); + } + + private static boolean isUnloadSupported(JdbcTableHandle table, List columns) + { + // Nothing to unload as there are no columns to be fetched from Redshift + if (table.getColumns().isPresent() && table.getColumns().get().isEmpty()) { + return false; + } + if (containsUnsupportedType(columns)) { + return false; + } + // Unload command doesn't support limit clause. However, Trino can implement the workaround of wrapping limit query as inner query. See https://github.com/trinodb/trino/issues/24480 + if (table.getLimit().isPresent()) { + return false; + } + if (containsFilterConditionOnDecimalTypeColumn(table)) { + return false; + } + return true; + } + + // Unsupported unload command data types when using Parquet output file format + private static boolean containsUnsupportedType(List columns) + { + // ERROR: UNLOAD varbyte column "col_0" is only supported for TEXT/CSV. + // ERROR: UNLOAD time without time zone column "value" is only supported for TEXT/CSV. + return columns.stream().anyMatch(column -> column.getColumnType() instanceof TimeType || column.getColumnType() instanceof VarbinaryType); + } + + // Redshift driver generates incorrect cast precision in select query for filter condition on decimal columns. See https://github.com/aws/amazon-redshift-jdbc-driver/issues/129 + private static boolean containsFilterConditionOnDecimalTypeColumn(JdbcTableHandle table) + { + if (table.getConstraint().getDomains() + .map(domains -> domains.keySet().stream().anyMatch(column -> ((JdbcColumnHandle) column).getColumnType() instanceof DecimalType)) + .orElse(false)) { + return true; + } + return table.getConstraintExpressions().stream() + .flatMap(expression -> expression.parameters().stream()) + .anyMatch(parameter -> parameter.getType() instanceof DecimalType); + } +} diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadConnector.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadConnector.java new file mode 100644 index 000000000000..c3a17e9642e5 --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadConnector.java @@ -0,0 +1,185 @@ +/* + * 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.redshift; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Inject; +import io.airlift.bootstrap.LifeCycleManager; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.filesystem.s3.FileSystemS3; +import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorMetadata; +import io.trino.plugin.base.metrics.FileFormatDataSourceStats; +import io.trino.plugin.base.session.SessionPropertiesProvider; +import io.trino.plugin.jdbc.JdbcTransactionManager; +import io.trino.plugin.jdbc.TablePropertiesProvider; +import io.trino.spi.connector.Connector; +import io.trino.spi.connector.ConnectorAccessControl; +import io.trino.spi.connector.ConnectorCapabilities; +import io.trino.spi.connector.ConnectorMetadata; +import io.trino.spi.connector.ConnectorPageSinkProvider; +import io.trino.spi.connector.ConnectorPageSourceProvider; +import io.trino.spi.connector.ConnectorRecordSetProvider; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorSplitManager; +import io.trino.spi.connector.ConnectorTransactionHandle; +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; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Sets.immutableEnumSet; +import static io.trino.spi.connector.ConnectorCapabilities.NOT_NULL_COLUMN_CONSTRAINT; +import static java.util.Objects.requireNonNull; + +public class RedshiftUnloadConnector + implements Connector +{ + private final LifeCycleManager lifeCycleManager; + private final ConnectorSplitManager jdbcSplitManager; + private final ConnectorPageSinkProvider jdbcPageSinkProvider; + private final Optional accessControl; + private final Set procedures; + private final Set connectorTableFunctions; + private final List> sessionProperties; + private final List> tableProperties; + private final JdbcTransactionManager transactionManager; + private final RedshiftPageSourceProvider pageSourceProvider; + + @Inject + public RedshiftUnloadConnector( + LifeCycleManager lifeCycleManager, + ConnectorSplitManager jdbcSplitManager, + ConnectorRecordSetProvider jdbcRecordSetProvider, + ConnectorPageSinkProvider jdbcPageSinkProvider, + Optional accessControl, + Set procedures, + Set connectorTableFunctions, + Set sessionProperties, + Set tableProperties, + JdbcTransactionManager transactionManager, + @FileSystemS3 TrinoFileSystemFactory fileSystemFactory, + FileFormatDataSourceStats fileFormatDataSourceStats) + { + this.lifeCycleManager = requireNonNull(lifeCycleManager, "lifeCycleManager is null"); + this.jdbcSplitManager = requireNonNull(jdbcSplitManager, "jdbcSplitManager is null"); + this.jdbcPageSinkProvider = requireNonNull(jdbcPageSinkProvider, "jdbcPageSinkProvider is null"); + this.accessControl = requireNonNull(accessControl, "accessControl is null"); + this.procedures = ImmutableSet.copyOf(requireNonNull(procedures, "procedures is null")); + this.connectorTableFunctions = ImmutableSet.copyOf(requireNonNull(connectorTableFunctions, "connectorTableFunctions is null")); + this.sessionProperties = sessionProperties.stream() + .flatMap(sessionPropertiesProvider -> sessionPropertiesProvider.getSessionProperties().stream()) + .collect(toImmutableList()); + this.tableProperties = tableProperties.stream() + .flatMap(tablePropertiesProvider -> tablePropertiesProvider.getTableProperties().stream()) + .collect(toImmutableList()); + this.transactionManager = requireNonNull(transactionManager, "transactionManager is null"); + this.pageSourceProvider = new RedshiftPageSourceProvider(jdbcRecordSetProvider, fileSystemFactory, fileFormatDataSourceStats); + } + + @Override + public ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLevel, boolean readOnly, boolean autoCommit) + { + return transactionManager.beginTransaction(isolationLevel, readOnly, autoCommit); + } + + @Override + public ConnectorMetadata getMetadata(ConnectorSession session, ConnectorTransactionHandle transaction) + { + return new ClassLoaderSafeConnectorMetadata(transactionManager.getMetadata(transaction), getClass().getClassLoader()); + } + + @Override + public void commit(ConnectorTransactionHandle transaction) + { + transactionManager.commit(transaction); + } + + @Override + public void rollback(ConnectorTransactionHandle transaction) + { + transactionManager.rollback(transaction); + } + + @Override + public ConnectorSplitManager getSplitManager() + { + return jdbcSplitManager; + } + + @Override + public ConnectorRecordSetProvider getRecordSetProvider() + { + // throwing this exception will ensure using the RedshiftPageSourceProvider, that supports two modes of operation + throw new UnsupportedOperationException(); + } + + @Override + public ConnectorPageSourceProvider getPageSourceProvider() + { + return pageSourceProvider; + } + + @Override + public ConnectorPageSinkProvider getPageSinkProvider() + { + return jdbcPageSinkProvider; + } + + @Override + public ConnectorAccessControl getAccessControl() + { + return accessControl.orElseThrow(UnsupportedOperationException::new); + } + + @Override + public Set getProcedures() + { + return procedures; + } + + @Override + public Set getTableFunctions() + { + return connectorTableFunctions; + } + + @Override + public List> getSessionProperties() + { + return sessionProperties; + } + + @Override + public List> getTableProperties() + { + return tableProperties; + } + + @Override + public final void shutdown() + { + lifeCycleManager.stop(); + } + + @Override + public Set getCapabilities() + { + return immutableEnumSet(NOT_NULL_COLUMN_CONSTRAINT); + } +} diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadJdbcQueryEventListener.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadJdbcQueryEventListener.java new file mode 100644 index 000000000000..5b3055a3db17 --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadJdbcQueryEventListener.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.redshift; + +import com.google.inject.Inject; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.filesystem.s3.FileSystemS3; +import io.trino.plugin.jdbc.JdbcQueryEventListener; +import io.trino.spi.connector.ConnectorSession; + +import java.io.IOException; +import java.io.UncheckedIOException; + +import static io.trino.plugin.redshift.RedshiftSessionProperties.isUnloadEnabled; +import static java.util.Objects.requireNonNull; + +public class RedshiftUnloadJdbcQueryEventListener + implements JdbcQueryEventListener +{ + private final TrinoFileSystemFactory fileSystemFactory; + private final String unloadLocation; + + @Inject + public RedshiftUnloadJdbcQueryEventListener(@FileSystemS3 TrinoFileSystemFactory fileSystemFactory, RedshiftConfig redshiftConfig) + { + this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); + this.unloadLocation = redshiftConfig.getUnloadLocation().orElseThrow(); + } + + @Override + public void beginQuery(ConnectorSession session) {} + + @Override + public void cleanupQuery(ConnectorSession session) + { + if (isUnloadEnabled(session)) { + try { + fileSystemFactory.create(session).deleteDirectory(Location.of(unloadLocation + "/" + session.getQueryId())); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } +} diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadSplit.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadSplit.java new file mode 100644 index 000000000000..049c6fc69b58 --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadSplit.java @@ -0,0 +1,47 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.redshift; + +import com.google.common.collect.ImmutableMap; +import io.trino.spi.connector.ConnectorSplit; + +import java.util.Map; + +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 record RedshiftUnloadSplit(String path, long length) + implements ConnectorSplit +{ + private static final int INSTANCE_SIZE = instanceSize(RedshiftUnloadSplit.class); + + public RedshiftUnloadSplit + { + requireNonNull(path, "path is null"); + } + + @Override + public Map getSplitInfo() + { + return ImmutableMap.of("path", path); + } + + @Override + public long getRetainedSizeInBytes() + { + return INSTANCE_SIZE + estimatedSizeOf(path) + sizeOf(length); + } +} diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadSplitSource.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadSplitSource.java new file mode 100644 index 000000000000..5acb5dd6bd21 --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftUnloadSplitSource.java @@ -0,0 +1,196 @@ +/* + * 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.redshift; + +import com.amazon.redshift.jdbc.RedshiftPreparedStatement; +import com.amazon.redshift.util.RedshiftException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.json.ObjectMapperProvider; +import io.airlift.log.Logger; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoInputFile; +import io.trino.filesystem.TrinoInputStream; +import io.trino.plugin.jdbc.JdbcClient; +import io.trino.plugin.jdbc.JdbcColumnHandle; +import io.trino.plugin.jdbc.JdbcTableHandle; +import io.trino.plugin.jdbc.PreparedQuery; +import io.trino.plugin.jdbc.QueryBuilder; +import io.trino.plugin.jdbc.logging.RemoteQueryModifier; +import io.trino.spi.TrinoException; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorSplit; +import io.trino.spi.connector.ConnectorSplitSource; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.airlift.units.Duration.nanosSince; +import static io.trino.plugin.redshift.RedshiftErrorCode.REDSHIFT_FILESYSTEM_ERROR; +import static io.trino.plugin.redshift.RedshiftErrorCode.REDSHIFT_S3_CROSS_REGION_UNSUPPORTED; +import static java.util.Objects.requireNonNull; + +public class RedshiftUnloadSplitSource + implements ConnectorSplitSource +{ + private static final Logger log = Logger.get(RedshiftUnloadSplitSource.class); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapperProvider().get(); + + private final JdbcClient jdbcClient; + private final QueryBuilder queryBuilder; + private final RemoteQueryModifier queryModifier; + private final Optional unloadAuthorization; + private final String unloadOutputPath; + private final TrinoFileSystem fileSystem; + private final CompletableFuture resultSetFuture; + + private boolean finished; + + public RedshiftUnloadSplitSource( + ExecutorService executor, + ConnectorSession session, + JdbcClient jdbcClient, + JdbcTableHandle jdbcTableHandle, + List columns, + QueryBuilder queryBuilder, + RemoteQueryModifier queryModifier, + String unloadLocation, + Optional unloadAuthorization, + TrinoFileSystem fileSystem) + { + requireNonNull(executor, "executor is null"); + requireNonNull(session, "session is null"); + this.jdbcClient = requireNonNull(jdbcClient, "jdbcClient is null"); + requireNonNull(jdbcTableHandle, "jdbcTableHandle is null"); + requireNonNull(columns, "columns is null"); + this.queryBuilder = requireNonNull(queryBuilder, "queryBuilder is null"); + this.queryModifier = requireNonNull(queryModifier, "queryModifier is null"); + this.unloadAuthorization = requireNonNull(unloadAuthorization, "unloadAuthorization is null"); + this.fileSystem = requireNonNull(fileSystem, "fileSystem is null"); + + String queryFragmentId = session.getQueryId() + "/" + UUID.randomUUID(); + this.unloadOutputPath = unloadLocation + "/" + queryFragmentId + "/"; + + resultSetFuture = CompletableFuture.runAsync(() -> { + try (Connection connection = jdbcClient.getConnection(session)) { + String redshiftSelectSql = buildRedshiftSelectSql(session, connection, jdbcTableHandle, columns); + try (PreparedStatement statement = buildRedshiftUnloadSql(session, connection, columns, redshiftSelectSql, unloadOutputPath)) { + // Exclusively set readOnly to false to avoid query failing with "ERROR: transaction is read-only". + connection.setReadOnly(false); + log.debug("Executing: %s", statement); + long start = System.nanoTime(); + statement.execute(); // Return value of `statement.execute()` is not useful to determine whether UNLOAD command produced any result as it always return false. + log.info("Redshift UNLOAD command for %s query took %s", queryFragmentId, nanosSince(start)); + } + } + catch (SQLException e) { + if (e instanceof RedshiftException && e.getMessage() != null && e.getMessage().contains("The S3 bucket addressed by the query is in a different region from this cluster")) { + throw new TrinoException(REDSHIFT_S3_CROSS_REGION_UNSUPPORTED, "Redshift cluster and S3 bucket in different regions is not supported", e); + } + throw new RuntimeException(e); + } + }, executor); + } + + @Override + public CompletableFuture getNextBatch(int maxSize) + { + return resultSetFuture + .thenApply(_ -> { + ConnectorSplitBatch connectorSplitBatch = new ConnectorSplitBatch(readUnloadedFilePaths().stream() + .map(fileInfo -> (ConnectorSplit) new RedshiftUnloadSplit(fileInfo.path, fileInfo.size)) + .collect(toImmutableList()), true); + finished = true; + return connectorSplitBatch; + }); + } + + @Override + public void close() + { + resultSetFuture.cancel(true); + } + + @Override + public boolean isFinished() + { + return finished; + } + + private String buildRedshiftSelectSql(ConnectorSession session, Connection connection, JdbcTableHandle table, List columns) + throws SQLException + { + PreparedQuery preparedQuery = jdbcClient.prepareQuery(session, table, Optional.empty(), columns, ImmutableMap.of()); + String selectQuerySql; + try (PreparedStatement openTelemetryPreparedStatement = queryBuilder.prepareStatement(jdbcClient, session, connection, preparedQuery, Optional.of(columns.size()))) { + RedshiftPreparedStatement redshiftPreparedStatement = openTelemetryPreparedStatement.unwrap(RedshiftPreparedStatement.class); + selectQuerySql = redshiftPreparedStatement.toString(); + } + return queryModifier.apply(session, selectQuerySql); + } + + private PreparedStatement buildRedshiftUnloadSql(ConnectorSession session, Connection connection, List columns, String redshiftSelectSql, String unloadOutputPath) + throws SQLException + { + String unloadSql = "UNLOAD ('%s') TO '%s' IAM_ROLE %s FORMAT PARQUET MAXFILESIZE 64MB MANIFEST VERBOSE".formatted( + escapeUnloadIllegalCharacters(redshiftSelectSql), + unloadOutputPath, + unloadAuthorization.map("'%s'"::formatted).orElse("DEFAULT")); + return queryBuilder.prepareStatement(jdbcClient, session, connection, new PreparedQuery(unloadSql, List.of()), Optional.of(columns.size())); + } + + private List readUnloadedFilePaths() + { + Location manifestLocation = Location.of(unloadOutputPath + "manifest"); + TrinoInputFile inputFile = fileSystem.newInputFile(manifestLocation); + JsonNode outputFileEntries; + try (TrinoInputStream inputStream = inputFile.newStream()) { + byte[] manifestContent = inputStream.readAllBytes(); + outputFileEntries = OBJECT_MAPPER.readTree(manifestContent).path("entries"); + } + // manifest is not generated if unload query doesn't produce any results. + // Rely on the catching `FileNotFoundException` as opposed to calling `TrinoInputFile#exists` for determining absence of manifest file as `TrinoInputFile#exists` adds additional call to S3. + catch (FileNotFoundException e) { + return ImmutableList.of(); + } + catch (IOException e) { + throw new TrinoException(REDSHIFT_FILESYSTEM_ERROR, e); + } + ImmutableList.Builder unloadedFilePaths = ImmutableList.builder(); + outputFileEntries.elements() + .forEachRemaining(fileInfo -> unloadedFilePaths.add(new FileInfo(fileInfo.get("url").asText(), fileInfo.get("meta").get("content_length").longValue()))); + return unloadedFilePaths.build(); + } + + private static String escapeUnloadIllegalCharacters(String value) + { + return value + .replace("'", "''") // escape single quotes with single quotes + .replace("\\b", "\\\\b"); // escape backspace with backslash + } + + private record FileInfo(String path, long size) {} +} diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/TrinoParquetDataSource.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/TrinoParquetDataSource.java new file mode 100644 index 000000000000..57d5a1f7fc9d --- /dev/null +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/TrinoParquetDataSource.java @@ -0,0 +1,68 @@ +/* + * 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.redshift; + +import io.airlift.slice.Slice; +import io.trino.filesystem.TrinoInput; +import io.trino.filesystem.TrinoInputFile; +import io.trino.parquet.AbstractParquetDataSource; +import io.trino.parquet.ParquetDataSourceId; +import io.trino.parquet.ParquetReaderOptions; +import io.trino.plugin.base.metrics.FileFormatDataSourceStats; + +import java.io.IOException; + +import static java.util.Objects.requireNonNull; + +// Copied as-is from io.trino.plugin.hive.parquet.TrinoParquetDataSource +public class TrinoParquetDataSource + extends AbstractParquetDataSource +{ + private final FileFormatDataSourceStats stats; + private final TrinoInput input; + + public TrinoParquetDataSource(TrinoInputFile file, ParquetReaderOptions options, FileFormatDataSourceStats stats) + throws IOException + { + super(new ParquetDataSourceId(file.location().toString()), file.length(), options); + this.stats = requireNonNull(stats, "stats is null"); + this.input = file.newInput(); + } + + @Override + public void close() + throws IOException + { + input.close(); + } + + @Override + protected Slice readTailInternal(int length) + throws IOException + { + long readStart = System.nanoTime(); + Slice tail = input.readTail(length); + stats.readDataBytesPerSecond(tail.length(), System.nanoTime() - readStart); + return tail; + } + + @Override + protected void readInternal(long position, byte[] buffer, int bufferOffset, int bufferLength) + throws IOException + { + long readStart = System.nanoTime(); + input.readFully(position, buffer, bufferOffset, bufferLength); + stats.readDataBytesPerSecond(bufferLength, System.nanoTime() - readStart); + } +} 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 e38fa4cf3d78..2856863c5b3a 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 @@ -26,9 +26,6 @@ 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 BaseRedshiftFailureRecoveryTest extends BaseJdbcFailureRecoveryTest { @@ -58,14 +55,6 @@ protected QueryRunner createQueryRunner( .build(); } - @Test - @Override - protected void testUpdateWithSubquery() - { - assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - abort("skipped"); - } - @Test @Override protected void testUpdate() diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/RedshiftQueryRunner.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/RedshiftQueryRunner.java index d1d806f0a2eb..c74274090d2d 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/RedshiftQueryRunner.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/RedshiftQueryRunner.java @@ -56,7 +56,7 @@ private RedshiftQueryRunner() {} private static final Logger log = Logger.get(RedshiftQueryRunner.class); private static final String S3_TPCH_TABLES_ROOT = requiredNonEmptySystemProperty("test.redshift.s3.tpch.tables.root"); - private static final String IAM_ROLE = requiredNonEmptySystemProperty("test.redshift.iam.role"); + public static final String IAM_ROLE = requiredNonEmptySystemProperty("test.redshift.iam.role"); private static final String TEST_CATALOG = "redshift"; private static final String CONNECTOR_NAME = "redshift"; diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftCastPushdown.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftCastPushdown.java index 7d86e743e085..5c1dabe79353 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftCastPushdown.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftCastPushdown.java @@ -579,8 +579,7 @@ protected List invalidCast() @Test void testCastPushdownWithCharConvertedToVarchar() { - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( TEST_SCHEMA + "." + "char_converted_to_varchar_", "(a char(4097))", // char(REDSHIFT_MAX_CHAR` + 1) in Trino is mapped to varchar(REDSHIFT_MAX_CHAR` + 1) in Redshift ImmutableList.of("'hello'"))) { 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 6a507d29d87b..2c7498cb19d4 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 @@ -28,7 +28,9 @@ public class TestRedshiftConfig public void testDefaults() { assertRecordedDefaults(recordDefaults(RedshiftConfig.class) - .setFetchSize(null)); + .setFetchSize(null) + .setUnloadLocation(null) + .setUnloadIamRole(null)); } @Test @@ -36,10 +38,14 @@ public void testExplicitPropertyMappings() { Map properties = ImmutableMap.builder() .put("redshift.fetch-size", "2000") + .put("redshift.unload-location", "s3://bucket") + .put("redshift.unload-iam-role", "arn:aws:iam::123456789000:role/redshift_iam_role") .buildOrThrow(); RedshiftConfig expected = new RedshiftConfig() - .setFetchSize(2000); + .setFetchSize(2000) + .setUnloadLocation("s3://bucket") + .setUnloadIamRole("arn:aws:iam::123456789000:role/redshift_iam_role"); assertFullMapping(properties, expected); } 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 29f53a68514a..df7eb44513ff 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 @@ -273,7 +273,7 @@ private void testReadNullFromView(String redshiftType, String trinoType, boolean @Test public void testRedshiftAddNotNullColumn() { - try (TestTable table = new TestTable(getQueryRunner()::execute, TEST_SCHEMA + ".test_add_column_", "(col int)")) { + try (TestTable table = newTrinoTable(TEST_SCHEMA + ".test_add_column_", "(col int)")) { assertThatThrownBy(() -> onRemoteDatabase().execute("ALTER TABLE " + table.getName() + " ADD COLUMN new_col int NOT NULL")) .hasMessageContaining("ERROR: ALTER TABLE ADD COLUMN defined as NOT NULL must have a non-null default expression"); } @@ -316,7 +316,7 @@ public void testRangeQueryConvertedToInClauseQuery() public void testDelete() { // The base tests is very slow because Redshift CTAS is really slow, so use a smaller test - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_delete_", "AS SELECT * FROM nation")) { // delete without matching any rows assertUpdate("DELETE FROM " + table.getName() + " WHERE nationkey < 0", 0); @@ -449,7 +449,7 @@ public void testCountDistinctWithStringTypes() .collect(toImmutableList()); String tableName = "distinct_strings" + randomNameSuffix(); - try (TestTable testTable = new TestTable(getQueryRunner()::execute, tableName, "(t_char CHAR(5), t_varchar VARCHAR(5))", rows)) { + try (TestTable testTable = newTrinoTable(tableName, "(t_char CHAR(5), t_varchar VARCHAR(5))", rows)) { // Single count(DISTINCT ...) can be pushed even down even if SUPPORTS_AGGREGATION_PUSHDOWN_COUNT_DISTINCT == false as GROUP BY assertThat(query("SELECT count(DISTINCT t_varchar) FROM " + testTable.getName())) .matches("VALUES BIGINT '6'") @@ -661,7 +661,7 @@ public void testDecimalAvgPushdownForMaximumDecimalScale() "12345789.9876543210", format("%s.%s", "1".repeat(28), "9".repeat(10))); - try (TestTable testTable = new TestTable(getQueryRunner()::execute, TEST_SCHEMA + ".test_agg_pushdown_avg_max_decimal", + try (TestTable testTable = newTrinoTable(TEST_SCHEMA + ".test_agg_pushdown_avg_max_decimal", "(t_decimal DECIMAL(38, 10))", rows)) { // Redshift avg rounds down decimal result which doesn't match Presto semantics assertThatThrownBy(() -> assertThat(query("SELECT avg(t_decimal) FROM " + testTable.getName())).isFullyPushedDown()) @@ -683,7 +683,7 @@ public void testDecimalAvgPushdownFoShortDecimalScale() "0.987654321234567890", format("0.%s", "1".repeat(18))); - try (TestTable testTable = new TestTable(getQueryRunner()::execute, TEST_SCHEMA + ".test_agg_pushdown_avg_max_decimal", + try (TestTable testTable = newTrinoTable(TEST_SCHEMA + ".test_agg_pushdown_avg_max_decimal", "(t_decimal DECIMAL(18, 18))", rows)) { assertThat(query("SELECT avg(t_decimal) FROM " + testTable.getName())).isFullyPushedDown(); } @@ -699,15 +699,13 @@ public void testInsertRowConcurrently() @Test public void testJoinPushdownWithImplicitCast() { - try (TestTable leftTable = new TestTable( - getQueryRunner()::execute, + try (TestTable leftTable = newTrinoTable( "left_table_", "(id int, c_boolean boolean, c_tinyint tinyint, c_smallint smallint, c_integer integer, c_bigint bigint, c_real real, c_double_precision double precision, c_decimal_10_2 decimal(10, 2), c_varchar_50 varchar(50))", ImmutableList.of( "(11, true, 12, 12, 12, 12, 12.34, 12.34, 12.34, 'India')", "(12, false, 123, 123, 123, 123, 123.67, 123.67, 123.67, 'Poland')")); - TestTable rightTable = new TestTable( - getQueryRunner()::execute, + TestTable rightTable = newTrinoTable( "right_table_", "(id int, c_boolean boolean, c_tinyint tinyint, c_smallint smallint, c_integer integer, c_bigint bigint, c_real real, c_double_precision double precision, c_decimal_10_2 decimal(10, 2), c_varchar_100 varchar(100), c_varchar varchar)", ImmutableList.of( 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 9d4fdaad3e80..596ec7ffb1a1 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 @@ -35,4 +35,22 @@ public void testCreateConnector() "bootstrap.quiet", "true"), new TestingConnectorContext()).shutdown(); } + + @Test + public void testCreateUnloadConnector() + { + Plugin plugin = new RedshiftPlugin(); + ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:redshift:test", + "redshift.unload-location", "s3://bucket/path", + "redshift.unload-iam-role", "role", + "s3.aws-access-key", "access-key", + "s3.aws-secret-key", "secret-key", + "s3.region", "region", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); + } } diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftUnload.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftUnload.java new file mode 100644 index 000000000000..fd86bdcef45c --- /dev/null +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftUnload.java @@ -0,0 +1,232 @@ +/* + * 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.redshift; + +import com.google.common.collect.ImmutableList; +import io.trino.Session; +import io.trino.operator.OperatorInfo; +import io.trino.operator.SplitOperatorInfo; +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.QueryRunner; +import io.trino.testing.sql.SqlExecutor; +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 software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.MoreCollectors.onlyElement; +import static io.trino.plugin.redshift.RedshiftQueryRunner.IAM_ROLE; +import static io.trino.plugin.redshift.TestingRedshiftServer.TEST_SCHEMA; +import static io.trino.testing.TestingNames.randomNameSuffix; +import static io.trino.testing.TestingProperties.requiredNonEmptySystemProperty; +import static io.trino.testing.TestingSession.testSessionBuilder; +import static io.trino.tpch.TpchTable.NATION; +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) +final class TestRedshiftUnload + extends AbstractTestQueryFramework +{ + private static final String S3_UNLOAD_ROOT = requiredNonEmptySystemProperty("test.redshift.s3.unload.root"); + private static final String AWS_REGION = requiredNonEmptySystemProperty("test.redshift.aws.region"); + private static final String AWS_ACCESS_KEY = requiredNonEmptySystemProperty("test.redshift.aws.access-key"); + private static final String AWS_SECRET_KEY = requiredNonEmptySystemProperty("test.redshift.aws.secret-key"); + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + return RedshiftQueryRunner.builder() + .setConnectorProperties( + Map.of( + "redshift.unload-location", S3_UNLOAD_ROOT, + "redshift.unload-iam-role", IAM_ROLE, + "s3.region", AWS_REGION, + "s3.aws-access-key", AWS_ACCESS_KEY, + "s3.aws-secret-key", AWS_SECRET_KEY)) + .setInitialTables(List.of(NATION)) + .build(); + } + + @Test + void testUnloadEnabled() + { + assertQuery( + "SHOW SESSION LIKE 'redshift.unload_enabled'", + "VALUES ('redshift.unload_enabled', 'true', 'true', 'boolean', 'Use UNLOAD for reading query results')"); + } + + @Test + void testUnload() + { + assertQueryStats( + getSession(), + """ + SELECT nationkey, name FROM nation WHERE regionkey = 0 + UNION + SELECT nationkey, name FROM nation WHERE regionkey = 1 + """, + queryStats -> { + List> splitInfos = + queryStats.getOperatorSummaries() + .stream() + .filter(summary -> summary.getOperatorType().startsWith("TableScanOperator")) + .map(operatorStat -> ((SplitOperatorInfo) operatorStat.getInfo()).getSplitInfo()) + .collect(toImmutableList()); + splitInfos.forEach(splitInfo -> assertThat(splitInfo.get("path")).matches("%s/.*/.*/.*.parquet.*".formatted(S3_UNLOAD_ROOT))); + String unloadedFilePath = splitInfos.getFirst().get("path"); + assertThat(unloadedFilePath).matches("%s/.*/.*/.*.parquet.*".formatted(S3_UNLOAD_ROOT)); + try (S3Client s3 = S3Client.builder() + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(AWS_ACCESS_KEY, AWS_SECRET_KEY))) + .region(Region.of(AWS_REGION)) + .build()) { + URI s3Path = URI.create(unloadedFilePath.substring(0, unloadedFilePath.lastIndexOf("/", unloadedFilePath.lastIndexOf("/") - 1))); + assertThat(s3.listObjectsV2(request -> request.bucket(s3Path.getHost()).prefix(s3Path.getPath().substring(1))).contents()).isEmpty(); + } + }, + results -> assertThat(results.getRowCount()).isEqualTo(10)); + } + + @Test + void testUnloadDisabled() + { + Session unloadDisabledSession = testSessionBuilder(getSession()) + .setCatalogSessionProperty("redshift", "unload_enabled", "false") + .build(); + assertQueryStats( + unloadDisabledSession, + "SELECT nationkey, name FROM nation WHERE regionkey = 0", + queryStats -> { + OperatorInfo operatorInfo = queryStats.getOperatorSummaries() + .stream() + .filter(summary -> summary.getOperatorType().startsWith("TableScanOperator")) + .collect(onlyElement()) + .getInfo(); + assertThat(operatorInfo).isNull(); + }, + results -> assertThat(results.getRowCount()).isEqualTo(5)); + } + + @Test + void testUnloadProduceEmptyResults() + { + assertQueryStats( + getSession(), + "SELECT * FROM nation WHERE name = 'INVALID'", + queryStats -> { + OperatorInfo operatorInfo = queryStats.getOperatorSummaries() + .stream() + .filter(summary -> summary.getOperatorType().startsWith("TableScanOperator")) + .collect(onlyElement()) + .getInfo(); + assertThat(operatorInfo).isNull(); + }, + results -> assertThat(results.getRowCount()).isEqualTo(0)); + } + + @Test + void testUnloadFallbackToJdbc() + { + // Fallback to JDBC as limit clause is not supported by UNLOAD + assertQueryStats( + getSession(), + "SELECT nationkey, name FROM nation WHERE regionkey = 0 LIMIT 1", + queryStats -> { + OperatorInfo operatorInfo = queryStats.getOperatorSummaries() + .stream() + .filter(summary -> summary.getOperatorType().startsWith("TableScanOperator")) + .collect(onlyElement()) + .getInfo(); + assertThat(operatorInfo).isNull(); + }, + results -> assertThat(results.getRowCount()).isEqualTo(1)); + } + + @Test + void testColumnName() + { + List columnNames = ImmutableList.builder() + .add("lowercase") + .add("UPPERCASE") + .add("MixedCase") + .add("an_underscore") + .add("a-hyphen-minus") // ASCII '-' is HYPHEN-MINUS in Unicode + .add("a space") + .add("atrailingspace ") + .add(" aleadingspace") + .add("a.dot") + .add("a,comma") + .add("a:colon") + .add("a;semicolon") + .add("an@at") + // .add("a\"quote") // TODO escape "(double quotes) in UNLOAD manifest(manifest json contains unescaped double quotes in field value `"name": "a"quote"`) + .add("an'apostrophe") + .add("a`backtick`") + .add("a/slash`") + .add("a\\backslash`") + .add("adigit0") + .add("0startwithdigit") + .add("カラム") + .build(); + for (String columnName : columnNames) { + testColumnName(columnName, requiresDelimiting(columnName)); + } + } + + private void testColumnName(String columnName, boolean delimited) + { + String nameInSql = toColumnNameInSql(columnName, delimited); + String tableNamePrefix = "tcn_" + nameInSql.toLowerCase(ENGLISH).replaceAll("[^a-z0-9]", "") + randomNameSuffix(); + + try (TestTable table = new TestTable( + onRemoteDatabase(), + TEST_SCHEMA + "." + tableNamePrefix, + "(%s varchar(50))".formatted(nameInSql), + ImmutableList.of("'abc'"))) { + assertQuery("SELECT " + nameInSql + " FROM " + table.getName(), "VALUES ('abc')"); + } + } + + private static String toColumnNameInSql(String columnName, boolean delimited) + { + String nameInSql = columnName; + if (delimited) { + nameInSql = "\"" + columnName.replace("\"", "\"\"") + "\""; + } + return nameInSql; + } + + private static boolean requiresDelimiting(String identifierName) + { + return !identifierName.matches("[a-zA-Z][a-zA-Z0-9_]*"); + } + + private static SqlExecutor onRemoteDatabase() + { + return TestingRedshiftServer::executeInRedshift; + } +} diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftUnloadTypeMapping.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftUnloadTypeMapping.java new file mode 100644 index 000000000000..3a9275550a97 --- /dev/null +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftUnloadTypeMapping.java @@ -0,0 +1,54 @@ +/* + * 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.redshift; + +import com.google.common.collect.ImmutableMap; +import io.trino.testing.QueryRunner; + +import java.util.Map; + +import static io.trino.plugin.redshift.RedshiftQueryRunner.IAM_ROLE; +import static io.trino.plugin.redshift.TestingRedshiftServer.JDBC_PASSWORD; +import static io.trino.plugin.redshift.TestingRedshiftServer.JDBC_URL; +import static io.trino.plugin.redshift.TestingRedshiftServer.JDBC_USER; +import static io.trino.testing.TestingProperties.requiredNonEmptySystemProperty; + +public class TestRedshiftUnloadTypeMapping + extends TestRedshiftTypeMapping +{ + private static final String S3_UNLOAD_ROOT = requiredNonEmptySystemProperty("test.redshift.s3.unload.root"); + private static final String AWS_REGION = requiredNonEmptySystemProperty("test.redshift.aws.region"); + private static final String AWS_ACCESS_KEY = requiredNonEmptySystemProperty("test.redshift.aws.access-key"); + private static final String AWS_SECRET_KEY = requiredNonEmptySystemProperty("test.redshift.aws.secret-key"); + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + Map properties = ImmutableMap.builder() + .put("redshift.unload-location", S3_UNLOAD_ROOT) + .put("redshift.unload-iam-role", IAM_ROLE) + .put("s3.region", AWS_REGION) + .put("s3.aws-access-key", AWS_ACCESS_KEY) + .put("s3.aws-secret-key", AWS_SECRET_KEY) + .put("connection-url", JDBC_URL) + .put("connection-user", JDBC_USER) + .put("connection-password", JDBC_PASSWORD) + .buildOrThrow(); + + return RedshiftQueryRunner.builder() + .setConnectorProperties(properties) + .build(); + } +} diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index 8da2752169e6..94f1b8862ffe 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 - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index af958eaa9ead..8103062282a9 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 - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index bf777aa166e4..80f6c73bce1e 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 6ba379deed51..dede72d518ee 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 @@ -207,7 +207,7 @@ public void testSingleStoreTinyint() @Override public void testInsertIntoNotNullColumn() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "insert_not_null", "(nullable_col INTEGER, not_null_col INTEGER NOT NULL)")) { + try (TestTable table = newTrinoTable("insert_not_null", "(nullable_col INTEGER, not_null_col INTEGER NOT NULL)")) { assertUpdate(format("INSERT INTO %s (not_null_col) VALUES (2)", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES (NULL, 2)"); assertQueryFails(format("INSERT INTO %s (nullable_col) VALUES (1)", table.getName()), errorMessageForInsertIntoNotNullColumn("not_null_col")); @@ -220,7 +220,7 @@ public void testInsertIntoNotNullColumn() assertQueryFails(format("INSERT INTO %s (nullable_col) SELECT nationkey FROM nation WHERE regionkey < 0", table.getName()), ".*Field 'not_null_col' doesn't have a default value.*"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "commuted_not_null", "(nullable_col BIGINT, not_null_col BIGINT NOT NULL)")) { + try (TestTable table = newTrinoTable("commuted_not_null", "(nullable_col BIGINT, not_null_col BIGINT NOT NULL)")) { assertUpdate(format("INSERT INTO %s (not_null_col) VALUES (2)", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES (NULL, 2)"); // This is enforced by the engine and not the connector @@ -256,7 +256,7 @@ public void testAddNotNullColumn() .isInstanceOf(AssertionError.class) .hasMessage("Should fail to add not null column without a default value to a non-empty table"); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_nn_col", "(a_varchar varchar)")) { + try (TestTable table = newTrinoTable("test_add_nn_col", "(a_varchar varchar)")) { String tableName = table.getName(); assertUpdate("INSERT INTO " + tableName + " VALUES ('a')", 1); diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreLatestTypeMapping.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreLatestTypeMapping.java index db43725c72ea..447ff14babe2 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreLatestTypeMapping.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreLatestTypeMapping.java @@ -37,7 +37,7 @@ protected QueryRunner createQueryRunner() @Test void testUnsupportedTinyint() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "tpch.test_unsupported_tinyint", "(value tinyint)")) { + try (TestTable table = newTrinoTable("tpch.test_unsupported_tinyint", "(value tinyint)")) { assertThatThrownBy(() -> singleStoreServer.execute(format("INSERT INTO %s VALUES (-129)", table.getName()))) .hasMessageContaining("Out of range value"); assertThatThrownBy(() -> singleStoreServer.execute(format("INSERT INTO %s VALUES (128)", table.getName()))) @@ -48,7 +48,7 @@ void testUnsupportedTinyint() @Test void testUnsupportedSmallint() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "tpch.test_unsupported_smallint", "(value smallint)")) { + try (TestTable table = newTrinoTable("tpch.test_unsupported_smallint", "(value smallint)")) { assertThatThrownBy(() -> singleStoreServer.execute(format("INSERT INTO %s VALUES (-32769)", table.getName()))) .hasMessageContaining("Out of range value"); assertThatThrownBy(() -> singleStoreServer.execute(format("INSERT INTO %s VALUES (32768)", table.getName()))) @@ -59,7 +59,7 @@ void testUnsupportedSmallint() @Test void testUnsupportedInteger() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "tpch.test_unsupported_integer", "(value integer)")) { + try (TestTable table = newTrinoTable("tpch.test_unsupported_integer", "(value integer)")) { assertThatThrownBy(() -> singleStoreServer.execute(format("INSERT INTO %s VALUES (-2147483649)", table.getName()))) .hasMessageContaining("Out of range value"); assertThatThrownBy(() -> singleStoreServer.execute(format("INSERT INTO %s VALUES (2147483648)", table.getName()))) @@ -70,7 +70,7 @@ void testUnsupportedInteger() @Test void testUnsupportedBigint() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "tpch.test_unsupported_bigint", "(value bigint)")) { + try (TestTable table = newTrinoTable("tpch.test_unsupported_bigint", "(value bigint)")) { assertThatThrownBy(() -> singleStoreServer.execute(format("INSERT INTO %s VALUES (-9223372036854775809)", table.getName()))) .hasMessageContaining("Out of range value"); assertThatThrownBy(() -> singleStoreServer.execute(format("INSERT INTO %s VALUES (9223372036854775808)", table.getName()))) diff --git a/plugin/trino-snowflake/pom.xml b/plugin/trino-snowflake/pom.xml index ff7f3b6cba6f..e9efdfe0c427 100644 --- a/plugin/trino-snowflake/pom.xml +++ b/plugin/trino-snowflake/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-spooling-filesystem/pom.xml b/plugin/trino-spooling-filesystem/pom.xml index 280761c86450..bd05fa96b7dc 100644 --- a/plugin/trino-spooling-filesystem/pom.xml +++ b/plugin/trino-spooling-filesystem/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index 33ff0e4c224e..2cb41eb6fa90 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 903c8b9c523e..ec67c6c1798d 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 @@ -363,7 +363,7 @@ public void testDeleteWithVarcharInequalityPredicate() // Override this because by enabling this flag SUPPORTS_PREDICATE_PUSHDOWN_WITH_VARCHAR_INEQUALITY, // we assume that we also support range pushdowns, but for now we only support 'not equal' pushdown, // so cannot enable this flag for now - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_varchar", "(col varchar(1))", ImmutableList.of("'a'", "'A'", "null"))) { + try (TestTable table = newTrinoTable("test_delete_varchar", "(col varchar(1))", ImmutableList.of("'a'", "'A'", "null"))) { assertUpdate("DELETE FROM " + table.getName() + " WHERE col != 'A'", 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES 'A', null"); } @@ -834,7 +834,7 @@ THEN INSERT(id) VALUES(SOURCE.id) public void testConstantUpdateWithVarcharInequalityPredicates() { // Sql Server supports push down predicate for not equal operator - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_update_varchar", "(col1 INT, col2 varchar(1))", ImmutableList.of("1, 'a'", "2, 'A'"))) { + try (TestTable table = newTrinoTable("test_update_varchar", "(col1 INT, col2 varchar(1))", ImmutableList.of("1, 'a'", "2, 'A'"))) { assertUpdate("UPDATE " + table.getName() + " SET col1 = 20 WHERE col2 != 'A'", 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES (20, 'a'), (2, 'A')"); } 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 4dbce2a01c03..ac8d2967479a 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 @@ -26,9 +26,6 @@ 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 BaseSqlServerFailureRecoveryTest extends BaseJdbcFailureRecoveryTest { @@ -58,14 +55,6 @@ protected QueryRunner createQueryRunner( .build(); } - @Test - @Override - protected void testUpdateWithSubquery() - { - assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - abort("skipped"); - } - @Test @Override protected void testUpdate() diff --git a/plugin/trino-teradata-functions/pom.xml b/plugin/trino-teradata-functions/pom.xml index bcec10794f54..61b82f22590f 100644 --- a/plugin/trino-teradata-functions/pom.xml +++ b/plugin/trino-teradata-functions/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-api/pom.xml b/plugin/trino-thrift-api/pom.xml index ac22279fcf77..32fc9779ec76 100644 --- a/plugin/trino-thrift-api/pom.xml +++ b/plugin/trino-thrift-api/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index 263e2d938447..37399a9f7cb9 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 - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index 1bca2e060feb..c28c1d49459f 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpcds/pom.xml b/plugin/trino-tpcds/pom.xml index 55c1c4622a9c..f9aa6929d6c0 100644 --- a/plugin/trino-tpcds/pom.xml +++ b/plugin/trino-tpcds/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpch/pom.xml b/plugin/trino-tpch/pom.xml index 614b7b2cd7cd..36067fff8132 100644 --- a/plugin/trino-tpch/pom.xml +++ b/plugin/trino-tpch/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-vertica/pom.xml b/plugin/trino-vertica/pom.xml index f741ebf9e8cf..a0ee11b9c890 100644 --- a/plugin/trino-vertica/pom.xml +++ b/plugin/trino-vertica/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-vertica/src/test/java/io/trino/plugin/vertica/TestVerticaConnectorTest.java b/plugin/trino-vertica/src/test/java/io/trino/plugin/vertica/TestVerticaConnectorTest.java index d65709629852..fb38c381de52 100644 --- a/plugin/trino-vertica/src/test/java/io/trino/plugin/vertica/TestVerticaConnectorTest.java +++ b/plugin/trino-vertica/src/test/java/io/trino/plugin/vertica/TestVerticaConnectorTest.java @@ -125,9 +125,8 @@ private void testJoinPushdown(JoinOperator joinOperator) Stream.of(notDistinctOperator)) .collect(toImmutableList()); - try (TestTable nationLowercaseTable = new TestTable( + try (TestTable nationLowercaseTable = newTrinoTable( // If a connector supports Join pushdown, but does not allow CTAS, we need to make the table creation here overridable. - getQueryRunner()::execute, "nation_lowercase", "AS SELECT nationkey, lower(name) name, regionkey FROM nation")) { // basic case diff --git a/pom.xml b/pom.xml index 356eda7ae09c..3d59e0397fbd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ io.airlift airbase - 205 + 209 io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT pom ${project.artifactId} @@ -148,7 +148,7 @@ 23 - 2024-12-06T21:31:48Z + 2024-12-17T21:29:19Z ERROR @@ -184,23 +184,24 @@ ${air.test.jvm.additional-arguments.default} - 292 + 294 2.9.6 4.13.2 1.12.0 1.12.780 4.17.0 7.7.1 - 106 + 107 1.22 11.1.0 1.15.1 v22.11.0 10.9.0 - 1.45.1 + 4.5.4 + 1.45.3 5.3.1 1.7.1 - 5.15.0 + 5.16.0 2.12.7 0.12.6 1.20.0 @@ -239,7 +240,7 @@ com.google.cloud libraries-bom - 26.51.0 + 26.52.0 pom import @@ -271,7 +272,7 @@ io.netty netty-bom - 4.1.115.Final + 4.1.116.Final pom import @@ -311,7 +312,7 @@ software.amazon.awssdk bom - 2.29.34 + 2.29.44 pom import @@ -504,7 +505,7 @@ com.github.luben zstd-jni - 1.5.6-8 + 1.5.6-9 @@ -522,7 +523,7 @@ com.google.cloud.bigdataoss gcs-connector - hadoop3-2.2.25 + hadoop3-2.2.26 shaded @@ -603,7 +604,7 @@ com.nimbusds nimbus-jose-jwt - 9.47 + 10.0 @@ -2038,7 +2039,7 @@ org.apache.logging.log4j log4j-api - 2.24.2 + 2.24.3 @@ -2521,6 +2522,15 @@ com/google/inject/Provider + + + java/io/File."<init>":(Ljava/lang/String;)V + java/io/FileInputStream."<init>":(Ljava/io/File;)V + java/io/FileInputStream."<init>":(Ljava/lang/String;)V + java/io/FileOutputStream."<init>":(Ljava/io/File;)V + java/io/FileOutputStream."<init>":(Ljava/lang/String;)V + java/io/File."<init>":(Ljava/net/URI;)V + java/io/File."<init>":(Ljava/io/File;Ljava/lang/String;)V @@ -2989,7 +2999,7 @@ io.github.gitflow-incremental-builder gitflow-incremental-builder - 4.5.4 + ${dep.gib.version} true true diff --git a/service/trino-proxy/pom.xml b/service/trino-proxy/pom.xml index 536e2a331dd0..ca569928a9b5 100644 --- a/service/trino-proxy/pom.xml +++ b/service/trino-proxy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/service/trino-verifier/pom.xml b/service/trino-verifier/pom.xml index 3f5d74b4bc8c..fb9151af5b0f 100644 --- a/service/trino-verifier/pom.xml +++ b/service/trino-verifier/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchmark-queries/pom.xml b/testing/trino-benchmark-queries/pom.xml index 51defaa2a581..a5f08759cad6 100644 --- a/testing/trino-benchmark-queries/pom.xml +++ b/testing/trino-benchmark-queries/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchto-benchmarks/pom.xml b/testing/trino-benchto-benchmarks/pom.xml index d84a4535521b..32443cd48126 100644 --- a/testing/trino-benchto-benchmarks/pom.xml +++ b/testing/trino-benchto-benchmarks/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index de6f1fb27f72..12a6f242568c 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/delta/TestDeltaFaultTolerantExecutionTest.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/delta/TestDeltaFaultTolerantExecutionTest.java index 0aeee0ac7346..bcffb9655fb2 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/delta/TestDeltaFaultTolerantExecutionTest.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/delta/TestDeltaFaultTolerantExecutionTest.java @@ -17,7 +17,7 @@ import io.trino.plugin.deltalake.DeltaLakeQueryRunner; import io.trino.plugin.exchange.filesystem.FileSystemExchangePlugin; import io.trino.plugin.exchange.filesystem.containers.MinioStorage; -import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.containers.Hive3MinioDataLake; import io.trino.testing.FaultTolerantExecutionConnectorTestHelper; import io.trino.testing.QueryRunner; @@ -38,7 +38,7 @@ public TestDeltaFaultTolerantExecutionTest() protected QueryRunner createQueryRunner() throws Exception { - HiveMinioDataLake hiveMinioDataLake = closeAfterClass(new HiveMinioDataLake(bucketName)); + Hive3MinioDataLake hiveMinioDataLake = closeAfterClass(new Hive3MinioDataLake(bucketName)); hiveMinioDataLake.start(); MinioStorage minioStorage = closeAfterClass(new MinioStorage(bucketName)); minioStorage.start(); diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionCoordinatorExcludedTest.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionCoordinatorExcludedTest.java index 39cfa116bc6d..34448e4ef58a 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionCoordinatorExcludedTest.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionCoordinatorExcludedTest.java @@ -67,7 +67,7 @@ public void testInsert() { String query = "SELECT name, nationkey, regionkey FROM nation"; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_", "AS " + query + " WITH NO DATA")) { + try (TestTable table = newTrinoTable("test_insert_", "AS " + query + " WITH NO DATA")) { assertQuery("SELECT count(*) FROM " + table.getName(), "SELECT 0"); assertUpdate("INSERT INTO " + table.getName() + " " + query, 25); diff --git a/testing/trino-plugin-reader/pom.xml b/testing/trino-plugin-reader/pom.xml index 23e4477fa9c2..1aa84a197865 100644 --- a/testing/trino-plugin-reader/pom.xml +++ b/testing/trino-plugin-reader/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests-groups/pom.xml b/testing/trino-product-tests-groups/pom.xml index f8f3a7416358..ee707c5d7a67 100644 --- a/testing/trino-product-tests-groups/pom.xml +++ b/testing/trino-product-tests-groups/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java b/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java index b31a26f78247..11c13365cdb9 100644 --- a/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java +++ b/testing/trino-product-tests-groups/src/main/java/io/trino/tests/product/TestGroups.java @@ -96,13 +96,12 @@ public final class TestGroups public static final String DELTA_LAKE_AZURE = "delta-lake-azure"; public static final String DELTA_LAKE_GCS = "delta-lake-gcs"; public static final String DELTA_LAKE_DATABRICKS = "delta-lake-databricks"; - public static final String DELTA_LAKE_DATABRICKS_104 = "delta-lake-databricks-104"; public static final String DELTA_LAKE_DATABRICKS_113 = "delta-lake-databricks-113"; public static final String DELTA_LAKE_DATABRICKS_122 = "delta-lake-databricks-122"; public static final String DELTA_LAKE_DATABRICKS_133 = "delta-lake-databricks-133"; public static final String DELTA_LAKE_DATABRICKS_143 = "delta-lake-databricks-143"; public static final String DATABRICKS_UNITY_HTTP_HMS = "databricks-unity-http-hms"; - public static final String DELTA_LAKE_EXCLUDE_91 = "delta-lake-exclude-91"; + public static final String DELTA_LAKE_EXCLUDE_104 = "delta-lake-exclude-104"; public static final String DELTA_LAKE_ALLUXIO_CACHING = "delta-lake-alluxio-caching"; public static final String HUDI = "hudi"; public static final String PARQUET = "parquet"; diff --git a/testing/trino-product-tests-launcher/pom.xml b/testing/trino-product-tests-launcher/pom.xml index 5fb09e66521f..9d4e65e5a002 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 - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/cli/EnvironmentUp.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/cli/EnvironmentUp.java index c8d4d016f4aa..1ffd6f84ac17 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/cli/EnvironmentUp.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/cli/EnvironmentUp.java @@ -113,6 +113,7 @@ public static class Execution private final Map extraOptions; private final PrintStream printStream; private final boolean debug; + private final boolean ipv6; @Inject public Execution(EnvironmentFactory environmentFactory, EnvironmentConfig environmentConfig, EnvironmentOptions options, EnvironmentUpOptions environmentUpOptions, PrintStream printStream) @@ -127,6 +128,7 @@ public Execution(EnvironmentFactory environmentFactory, EnvironmentConfig enviro this.extraOptions = ImmutableMap.copyOf(requireNonNull(environmentUpOptions.extraOptions, "environmentUpOptions.extraOptions is null")); this.printStream = requireNonNull(printStream, "printStream is null"); this.debug = options.debug; + this.ipv6 = options.ipv6; } @Override @@ -136,7 +138,8 @@ public Integer call() Environment.Builder builder = environmentFactory.get(environment, printStream, environmentConfig, extraOptions) .setContainerOutputMode(outputMode) .setLogsBaseDir(environmentLogPath) - .removeContainer(TESTS); + .removeContainer(TESTS) + .setIpv6(ipv6); if (withoutCoordinator) { builder.removeContainers(container -> isTrinoContainer(container.getLogicalName())); diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/cli/TestRun.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/cli/TestRun.java index 27c05f8bbc68..0bf5f559eb0b 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/cli/TestRun.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/cli/TestRun.java @@ -53,6 +53,7 @@ import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.testing.SystemEnvironmentUtils.isEnvSet; import static io.trino.tests.product.launcher.env.DockerContainer.cleanOrCreateHostPath; import static io.trino.tests.product.launcher.env.EnvironmentContainers.TESTS; import static io.trino.tests.product.launcher.env.EnvironmentListener.getStandardListeners; @@ -155,6 +156,7 @@ public static class Execution private final EnvironmentFactory environmentFactory; private final boolean debug; private final boolean debugSuspend; + private final boolean ipv6; private final JdkProvider jdkProvider; private final File testJar; private final File cliJar; @@ -185,6 +187,7 @@ public Execution( this.environmentFactory = requireNonNull(environmentFactory, "environmentFactory is null"); requireNonNull(environmentOptions, "environmentOptions is null"); this.debug = environmentOptions.debug; + this.ipv6 = environmentOptions.ipv6; this.debugSuspend = testRunOptions.debugSuspend; this.jdkProvider = requireNonNull(jdkProvider, "jdkProvider is null"); this.testJar = requireNonNull(testRunOptions.testJar, "testRunOptions.testJar is null"); @@ -322,7 +325,8 @@ private Environment getEnvironment() Environment.Builder builder = environmentFactory.get(environment, printStream, environmentConfig, extraOptions) .setContainerOutputMode(outputMode) .setStartupRetries(startupRetries) - .setLogsBaseDir(logsDirBase); + .setLogsBaseDir(logsDirBase) + .setIpv6(ipv6); builder.configureContainer(TESTS, this::mountReportsDir); builder.configureContainer(TESTS, container -> { @@ -335,7 +339,7 @@ private Environment getEnvironment() unsafelyExposePort(container, 5007); // debug port } - if (System.getenv("CONTINUOUS_INTEGRATION") != null) { + if (isEnvSet("CONTINUOUS_INTEGRATION")) { container.withEnv("CONTINUOUS_INTEGRATION", "true"); } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/Environment.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/Environment.java index 5d2253875c8d..e9dff54f4049 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/Environment.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/Environment.java @@ -103,6 +103,7 @@ public final class Environment private final Map containers; private final EnvironmentListener listener; private final boolean attached; + private final boolean ipv6; private final Map> configuredFeatures; private Environment( @@ -111,12 +112,14 @@ private Environment( Map containers, EnvironmentListener listener, boolean attached, + boolean ipv6, Map> configuredFeatures, List> startupLogs) { this.name = requireNonNull(name, "name is null"); this.startupRetries = startupRetries; this.containers = requireNonNull(containers, "containers is null"); + this.ipv6 = ipv6; this.listener = compose(requireNonNull(listener, "listener is null"), printStartupLogs(startupLogs)); this.attached = attached; this.configuredFeatures = requireNonNull(configuredFeatures, "configuredFeatures is null"); @@ -141,7 +144,7 @@ public Environment start() return Failsafe .with(retryPolicy) .with(executorService) - .get(this::tryStart); + .get(() -> tryStart()); } private Environment tryStart() @@ -159,7 +162,7 @@ private Environment tryStart() } // Create new network when environment tries to start - try (Network network = createNetwork(name)) { + try (Network network = createNetwork(name, ipv6)) { attachNetwork(containers, network); Startables.deepStart(containers).get(); @@ -372,12 +375,13 @@ private static void attachNetwork(Collection values, Network ne values.forEach(container -> container.withNetwork(network)); } - private static Network createNetwork(String environmentName) + private static Network createNetwork(String environmentName, boolean ipv6) { Network network = Network.builder() .createNetworkCmdModifier(createNetworkCmd -> createNetworkCmd .withName(PRODUCT_TEST_LAUNCHER_NETWORK) + .withEnableIpv6(ipv6) .withLabels(ImmutableMap.of( PRODUCT_TEST_LAUNCHER_STARTED_LABEL_NAME, PRODUCT_TEST_LAUNCHER_STARTED_LABEL_VALUE, PRODUCT_TEST_LAUNCHER_ENVIRONMENT_LABEL_NAME, environmentName))) @@ -399,6 +403,7 @@ public static class Builder private int startupRetries = 1; private Optional logsBaseDir = Optional.empty(); private boolean attached; + private boolean ipv6; public Builder(String name, PrintStream printStream) { @@ -649,6 +654,7 @@ public Environment build(EnvironmentListener listener) containers, listener, attached, + ipv6, configuredFeatures, startupLogs.build()); } @@ -738,5 +744,11 @@ public Builder setAttached(boolean attached) this.attached = attached; return this; } + + public Builder setIpv6(boolean ipv6) + { + this.ipv6 = ipv6; + return this; + } } } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/EnvironmentModule.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/EnvironmentModule.java index 9597704239b8..606d06491fb6 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/EnvironmentModule.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/EnvironmentModule.java @@ -173,4 +173,12 @@ public boolean provideTracing(EnvironmentOptions options) { return options.tracing; } + + @Provides + @Singleton + @Ipv6 + public boolean provideIpv6(EnvironmentOptions options) + { + return options.ipv6; + } } 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 c1c989d66baa..b37dec6773c1 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 @@ -70,6 +70,9 @@ public final class EnvironmentOptions @Nullable public Path jdkDownloadPath; + @Option(names = "--ipv6", paramLabel = "", description = "Enable IPv6 networking") + public boolean ipv6; + @Option(names = "--bind", description = "Bind exposed container ports to host ports, possible values: " + BIND_ON_HOST + ", " + DO_NOT_BIND + ", [port base number] " + DEFAULT_VALUE, defaultValue = BIND_ON_HOST, arity = "0..1", fallbackValue = BIND_ON_HOST) public void setBindOnHost(String value) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveRedirectionsProvider.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/Ipv6.java similarity index 53% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveRedirectionsProvider.java rename to testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/Ipv6.java index 6d595a964be7..ff9bec867a82 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveRedirectionsProvider.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/Ipv6.java @@ -11,15 +11,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package io.trino.tests.product.launcher.env; -package io.trino.plugin.hive; +import com.google.inject.BindingAnnotation; -import io.trino.spi.connector.ConnectorSession; -import io.trino.spi.connector.TableScanRedirectApplicationResult; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; -import java.util.Optional; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; -public interface HiveRedirectionsProvider +@Retention(RUNTIME) +@Target({FIELD, PARAMETER, METHOD}) +@BindingAnnotation +public @interface Ipv6 { - Optional getTableScanRedirection(ConnectorSession session, HiveTableHandle tableHandle); } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/HydraIdentityProvider.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/HydraIdentityProvider.java index bfb9237365ca..3af695f4b646 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/HydraIdentityProvider.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/HydraIdentityProvider.java @@ -36,7 +36,7 @@ public class HydraIdentityProvider private static final int TTL_ACCESS_TOKEN_IN_SECONDS = 5; private static final int TTL_REFRESH_TOKEN_IN_SECONDS = 15; - private static final String HYDRA_IMAGE = "oryd/hydra:v1.10.6"; + private static final String HYDRA_IMAGE = "oryd/hydra:v1.11.10"; private static final String DSN = "postgres://hydra:mysecretpassword@hydra-db:5432/hydra?sslmode=disable"; private final PortBinder binder; private final DockerFiles.ResourceProvider configDir; diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/Standard.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/Standard.java index 0af233ec6868..619a0b77873b 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/Standard.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/Standard.java @@ -22,6 +22,7 @@ import io.trino.tests.product.launcher.env.Environment; import io.trino.tests.product.launcher.env.EnvironmentConfig; import io.trino.tests.product.launcher.env.EnvironmentContainers; +import io.trino.tests.product.launcher.env.Ipv6; import io.trino.tests.product.launcher.env.ServerPackage; import io.trino.tests.product.launcher.env.Tracing; import io.trino.tests.product.launcher.env.jdk.JdkProvider; @@ -90,6 +91,7 @@ public final class Standard private final File serverPackage; private final boolean debug; private final boolean tracing; + private final boolean ipv6; @Inject public Standard( @@ -99,7 +101,8 @@ public Standard( @ServerPackage File serverPackage, JdkProvider jdkProvider, @Debug boolean debug, - @Tracing boolean tracing) + @Tracing boolean tracing, + @Ipv6 boolean ipv6) { this.dockerFiles = requireNonNull(dockerFiles, "dockerFiles is null"); this.portBinder = requireNonNull(portBinder, "portBinder is null"); @@ -107,6 +110,7 @@ public Standard( this.jdkProvider = requireNonNull(jdkProvider, "jdkProvider is null"); this.serverPackage = requireNonNull(serverPackage, "serverPackage is null"); this.debug = debug; + this.ipv6 = ipv6; this.tracing = tracing; checkArgument(serverPackage.getName().endsWith(".tar.gz"), "Currently only server .tar.gz package is supported"); } @@ -169,7 +173,7 @@ private DockerContainer createTracingCollector() private DockerContainer createTrinoCoordinator() { DockerContainer container = - createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, "ghcr.io/trinodb/testing/almalinux9-oj17:" + imagesVersion, COORDINATOR) + createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, ipv6, "ghcr.io/trinodb/testing/almalinux9-oj17:" + imagesVersion, COORDINATOR) .withCopyFileToContainer(forHostPath(dockerFiles.getDockerFilesHostPath("common/standard/access-control.properties")), CONTAINER_TRINO_ACCESS_CONTROL_PROPERTIES) .withCopyFileToContainer(forHostPath(dockerFiles.getDockerFilesHostPath("common/standard/config.properties")), CONTAINER_TRINO_CONFIG_PROPERTIES); @@ -188,7 +192,7 @@ private DockerContainer createTestsContainer() } @SuppressWarnings("resource") - public static DockerContainer createTrinoContainer(DockerFiles dockerFiles, File serverPackage, JdkProvider jdkProvider, boolean debug, boolean tracing, String dockerImageName, String logicalName) + public static DockerContainer createTrinoContainer(DockerFiles dockerFiles, File serverPackage, JdkProvider jdkProvider, boolean debug, boolean tracing, boolean ipv6, String dockerImageName, String logicalName) { DockerContainer container = new DockerContainer(dockerImageName, logicalName) .withNetworkAliases(logicalName + ".docker.cluster") @@ -215,6 +219,10 @@ public static DockerContainer createTrinoContainer(DockerFiles dockerFiles, File enableTrinoTracing(container); } + if (ipv6) { + enableTrinoIpv6(container); + } + return jdkProvider.applyTo(container); } @@ -289,6 +297,32 @@ private static void enableTrinoTracing(DockerContainer container) } } + private static void enableTrinoIpv6(DockerContainer container) + { + log.info("Setting IPv6 as preferred networking stack for container: '%s'", container.getLogicalName()); + + try { + FileAttribute> rwx = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxrwxrwx")); + Path script = Files.createTempFile("enable-ipv6-stack", ".sh", rwx); + script.toFile().deleteOnExit(); + Files.writeString( + script, + format( + "#!/bin/bash\n" + + "ipv6=$(hostname -I | awk '{print $2}')\n" + + "IPv6 address of the node: ${ipv6}\n" + + "echo '-Djava.net.preferIPv6Addresses=true' >> '%s'\n" + + "echo \"node.internal-address=${ipv6}\" >> '%s'\n", + CONTAINER_TRINO_JVM_CONFIG, + CONTAINER_TRINO_CONFIG_PROPERTIES), + UTF_8); + container.withCopyFileToContainer(forHostPath(script), "/docker/presto-init.d/enable-ipv6-stack.sh"); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + private static void enableTrinoJmxRmi(DockerContainer dockerContainer) { String logicalName = dockerContainer.getLogicalName(); diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/StandardMultinode.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/StandardMultinode.java index a6cc8b395afa..647c9f577783 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/StandardMultinode.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/common/StandardMultinode.java @@ -20,6 +20,7 @@ import io.trino.tests.product.launcher.env.DockerContainer; import io.trino.tests.product.launcher.env.Environment; import io.trino.tests.product.launcher.env.EnvironmentConfig; +import io.trino.tests.product.launcher.env.Ipv6; import io.trino.tests.product.launcher.env.ServerPackage; import io.trino.tests.product.launcher.env.Tracing; import io.trino.tests.product.launcher.env.jdk.JdkProvider; @@ -46,6 +47,7 @@ public class StandardMultinode private final JdkProvider jdkProvider; private final boolean debug; private final boolean tracing; + private final boolean ipv6; @Inject public StandardMultinode( @@ -55,7 +57,8 @@ public StandardMultinode( @ServerPackage File serverPackage, JdkProvider jdkProvider, @Debug boolean debug, - @Tracing boolean tracing) + @Tracing boolean tracing, + @Ipv6 boolean ipv6) { this.standard = requireNonNull(standard, "standard is null"); this.dockerFiles = requireNonNull(dockerFiles, "dockerFiles is null"); @@ -65,6 +68,7 @@ public StandardMultinode( this.serverPackage = requireNonNull(serverPackage, "serverPackage is null"); this.debug = debug; this.tracing = tracing; + this.ipv6 = ipv6; checkArgument(serverPackage.getName().endsWith(".tar.gz"), "Currently only server .tar.gz package is supported"); } @@ -85,7 +89,7 @@ public void extendEnvironment(Environment.Builder builder) @SuppressWarnings("resource") private DockerContainer createTrinoWorker() { - return createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, "ghcr.io/trinodb/testing/almalinux9-oj17:" + imagesVersion, WORKER) + return createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, ipv6, "ghcr.io/trinodb/testing/almalinux9-oj17:" + imagesVersion, WORKER) .withCopyFileToContainer(forHostPath(configDir.getPath("multinode-worker-config.properties")), CONTAINER_TRINO_CONFIG_PROPERTIES); } } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/AbstractSinglenodeDeltaLakeDatabricks.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/AbstractSinglenodeDeltaLakeDatabricks.java index ef7666f5cbb1..4f9ce1af383a 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/AbstractSinglenodeDeltaLakeDatabricks.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/AbstractSinglenodeDeltaLakeDatabricks.java @@ -21,6 +21,7 @@ import java.io.File; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.launcher.env.EnvironmentContainers.COORDINATOR; import static io.trino.tests.product.launcher.env.EnvironmentContainers.TESTS; import static io.trino.tests.product.launcher.env.EnvironmentContainers.configureTempto; @@ -39,22 +40,22 @@ public abstract class AbstractSinglenodeDeltaLakeDatabricks private final DockerFiles dockerFiles; - abstract String databricksTestJdbcUrl(); - public AbstractSinglenodeDeltaLakeDatabricks(Standard standard, DockerFiles dockerFiles) { super(standard); this.dockerFiles = requireNonNull(dockerFiles, "dockerFiles is null"); } + abstract String databricksTestJdbcUrl(); + @Override public void extendEnvironment(Environment.Builder builder) { String databricksTestJdbcUrl = databricksTestJdbcUrl(); - String databricksTestLogin = requireNonNull(System.getenv("DATABRICKS_LOGIN"), "Environment DATABRICKS_LOGIN was not set"); - String databricksTestToken = requireNonNull(System.getenv("DATABRICKS_TOKEN"), "Environment DATABRICKS_TOKEN was not set"); - String awsRegion = requireNonNull(System.getenv("AWS_REGION"), "Environment AWS_REGION was not set"); - String s3Bucket = requireNonNull(System.getenv("S3_BUCKET"), "Environment S3_BUCKET was not set"); + String databricksTestLogin = requireEnv("DATABRICKS_LOGIN"); + String databricksTestToken = requireEnv("DATABRICKS_TOKEN"); + String awsRegion = requireEnv("AWS_REGION"); + String s3Bucket = requireEnv("S3_BUCKET"); DockerFiles.ResourceProvider configDir = dockerFiles.getDockerFilesHostDirectory("conf/environment/singlenode-delta-lake-databricks"); builder.configureContainer(COORDINATOR, dockerContainer -> exportAWSCredentials(dockerContainer) diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAzure.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAzure.java index d31e6892a939..c46ef68e7356 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAzure.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAzure.java @@ -34,6 +34,7 @@ import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermissions; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.launcher.docker.ContainerUtil.forSelectedPorts; import static io.trino.tests.product.launcher.env.EnvironmentContainers.COORDINATOR; @@ -203,9 +204,4 @@ private Path getTemptoConfiguration(String schema) 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/env/environment/EnvMultinodeDatabricksHttpHms.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeDatabricksHttpHms.java index 7a5317b0195c..ce0c8b48115b 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeDatabricksHttpHms.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeDatabricksHttpHms.java @@ -23,6 +23,7 @@ import java.io.File; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.launcher.env.EnvironmentContainers.TESTS; import static io.trino.tests.product.launcher.env.EnvironmentContainers.configureTempto; import static io.trino.tests.product.launcher.env.EnvironmentContainers.isTrinoContainer; @@ -50,18 +51,18 @@ public EnvMultinodeDatabricksHttpHms(StandardMultinode standardMultinode, Docker @Override public void extendEnvironment(Environment.Builder builder) { - String databricksTestJdbcUrl = requireNonNull(getEnvVariable("DATABRICKS_UNITY_JDBC_URL"), "Environment DATABRICKS_UNITY_JDBC_URL was not set"); - String databricksTestLogin = requireNonNull(getEnvVariable("DATABRICKS_LOGIN"), "Environment DATABRICKS_LOGIN was not set"); - String databricksTestToken = requireNonNull(getEnvVariable("DATABRICKS_TOKEN"), "Environment DATABRICKS_TOKEN was not set"); - String awsRegion = requireNonNull(getEnvVariable("AWS_REGION"), "Environment AWS_REGION was not set"); + String databricksTestJdbcUrl = requireEnv("DATABRICKS_UNITY_JDBC_URL"); + String databricksTestLogin = requireEnv("DATABRICKS_LOGIN"); + String databricksTestToken = requireEnv("DATABRICKS_TOKEN"); + String awsRegion = requireEnv("AWS_REGION"); builder.configureContainers(container -> { if (isTrinoContainer(container.getLogicalName())) { exportAwsCredentials(container) .withEnv("AWS_REGION", awsRegion) .withEnv("DATABRICKS_TOKEN", databricksTestToken) - .withEnv("DATABRICKS_HOST", getEnvVariable("DATABRICKS_HOST")) - .withEnv("DATABRICKS_UNITY_CATALOG_NAME", getEnvVariable("DATABRICKS_UNITY_CATALOG_NAME")); + .withEnv("DATABRICKS_HOST", requireEnv("DATABRICKS_HOST")) + .withEnv("DATABRICKS_UNITY_CATALOG_NAME", requireEnv("DATABRICKS_UNITY_CATALOG_NAME")); } }); @@ -69,8 +70,8 @@ public void extendEnvironment(Environment.Builder builder) .withEnv("DATABRICKS_JDBC_URL", databricksTestJdbcUrl) .withEnv("DATABRICKS_LOGIN", databricksTestLogin) .withEnv("DATABRICKS_TOKEN", databricksTestToken) - .withEnv("DATABRICKS_UNITY_CATALOG_NAME", getEnvVariable("DATABRICKS_UNITY_CATALOG_NAME")) - .withEnv("DATABRICKS_UNITY_EXTERNAL_LOCATION", getEnvVariable("DATABRICKS_UNITY_EXTERNAL_LOCATION")) + .withEnv("DATABRICKS_UNITY_CATALOG_NAME", requireEnv("DATABRICKS_UNITY_CATALOG_NAME")) + .withEnv("DATABRICKS_UNITY_EXTERNAL_LOCATION", requireEnv("DATABRICKS_UNITY_EXTERNAL_LOCATION")) .withCopyFileToContainer( forHostPath(DATABRICKS_JDBC_PROVIDER.getAbsolutePath()), "/docker/jdbc/databricks-jdbc.jar")); @@ -83,15 +84,6 @@ public void extendEnvironment(Environment.Builder builder) configureTempto(builder, configDir); } - private static String getEnvVariable(String name) - { - String credentialValue = System.getenv(name); - if (credentialValue == null) { - throw new IllegalStateException(format("Environment variable %s not set", name)); - } - return credentialValue; - } - private DockerContainer exportAwsCredentials(DockerContainer container) { container = exportAwsCredential(container, "TRINO_AWS_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID", true); diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeGcs.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeGcs.java index 3fab018afb8c..19091983dd4d 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeGcs.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeGcs.java @@ -37,6 +37,7 @@ import java.util.Base64; import java.util.UUID; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.launcher.docker.ContainerUtil.forSelectedPorts; import static io.trino.tests.product.launcher.env.EnvironmentContainers.COORDINATOR; import static io.trino.tests.product.launcher.env.EnvironmentContainers.HADOOP; @@ -188,9 +189,4 @@ private Path getHiveSiteOverrideXml(String gcpStorageBucket) 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/env/environment/EnvMultinodeSnowflake.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeSnowflake.java index ac5694a0b835..ab1933931999 100644 --- 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 @@ -20,6 +20,7 @@ import io.trino.tests.product.launcher.env.common.Standard; import io.trino.tests.product.launcher.env.common.TestsEnvironment; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.launcher.env.EnvironmentContainers.isTrinoContainer; import static io.trino.tests.product.launcher.env.common.Standard.CONTAINER_TRINO_JVM_CONFIG; import static java.util.Objects.requireNonNull; @@ -57,9 +58,4 @@ public void extendEnvironment(Environment.Builder builder) builder.addConnector("snowflake", forHostPath(configDir.getPath("snowflake.properties"))); } - - 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/env/environment/EnvMultinodeTls.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTls.java index fc87550104be..71e996073176 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTls.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTls.java @@ -21,6 +21,7 @@ import io.trino.tests.product.launcher.env.Environment; import io.trino.tests.product.launcher.env.EnvironmentConfig; import io.trino.tests.product.launcher.env.EnvironmentProvider; +import io.trino.tests.product.launcher.env.Ipv6; import io.trino.tests.product.launcher.env.ServerPackage; import io.trino.tests.product.launcher.env.Tracing; import io.trino.tests.product.launcher.env.common.Hadoop; @@ -53,6 +54,7 @@ public final class EnvMultinodeTls private final File serverPackage; private final boolean debug; private final boolean tracing; + private final boolean ipv6; private final JdkProvider jdkProvider; @Inject @@ -65,7 +67,8 @@ public EnvMultinodeTls( @ServerPackage File serverPackage, JdkProvider jdkProvider, @Debug boolean debug, - @Tracing boolean tracing) + @Tracing boolean tracing, + @Ipv6 boolean ipv6) { super(ImmutableList.of(standard, hadoop)); this.dockerFiles = requireNonNull(dockerFiles, "dockerFiles is null"); @@ -75,6 +78,7 @@ public EnvMultinodeTls( this.serverPackage = requireNonNull(serverPackage, "serverPackage is null"); this.debug = debug; this.tracing = tracing; + this.ipv6 = ipv6; } @Override @@ -97,7 +101,7 @@ public void extendEnvironment(Environment.Builder builder) private DockerContainer createTrinoWorker(String workerName) { - return createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, "ghcr.io/trinodb/testing/almalinux9-oj17:" + imagesVersion, workerName) + return createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, ipv6, "ghcr.io/trinodb/testing/almalinux9-oj17:" + imagesVersion, workerName) .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withDomainName("docker.cluster")) .withCopyFileToContainer(forHostPath(dockerFiles.getDockerFilesHostPath("conf/environment/multinode-tls/config-worker.properties")), CONTAINER_TRINO_CONFIG_PROPERTIES) .withCopyFileToContainer(forHostPath(dockerFiles.getDockerFilesHostPath("common/hadoop/hive.properties")), CONTAINER_TRINO_HIVE_PROPERTIES) diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTlsKerberos.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTlsKerberos.java index 9d09ea8a5192..55583a81326a 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTlsKerberos.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTlsKerberos.java @@ -21,6 +21,7 @@ import io.trino.tests.product.launcher.env.Environment; import io.trino.tests.product.launcher.env.EnvironmentConfig; import io.trino.tests.product.launcher.env.EnvironmentProvider; +import io.trino.tests.product.launcher.env.Ipv6; import io.trino.tests.product.launcher.env.ServerPackage; import io.trino.tests.product.launcher.env.Tracing; import io.trino.tests.product.launcher.env.common.HadoopKerberos; @@ -52,6 +53,7 @@ public final class EnvMultinodeTlsKerberos private final File serverPackage; private final boolean debug; private final boolean tracing; + private final boolean ipv6; @Inject public EnvMultinodeTlsKerberos( @@ -62,10 +64,12 @@ public EnvMultinodeTlsKerberos( @ServerPackage File serverPackage, JdkProvider jdkProvider, @Debug boolean debug, - @Tracing boolean tracing) + @Tracing boolean tracing, + @Ipv6 boolean ipv6) { super(ImmutableList.of(standard, hadoopKerberos)); this.dockerFiles = requireNonNull(dockerFiles, "dockerFiles is null"); + this.ipv6 = ipv6; String hadoopBaseImage = config.getHadoopBaseImage(); String hadoopImagesVersion = config.getHadoopImagesVersion(); this.trinoDockerImageName = hadoopBaseImage + "-kerberized:" + hadoopImagesVersion; @@ -93,7 +97,7 @@ public void extendEnvironment(Environment.Builder builder) @SuppressWarnings("resource") private DockerContainer createTrinoWorker(String workerName) { - return createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, trinoDockerImageName, workerName) + return createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, ipv6, trinoDockerImageName, workerName) .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withDomainName("docker.cluster")) .withCopyFileToContainer(forHostPath(dockerFiles.getDockerFilesHostPath("conf/environment/multinode-tls-kerberos/config-worker.properties")), CONTAINER_TRINO_CONFIG_PROPERTIES) .withCopyFileToContainer(forHostPath(dockerFiles.getDockerFilesHostPath("conf/environment/multinode-tls-kerberos/hive.properties")), CONTAINER_TRINO_HIVE_PROPERTIES) diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTlsKerberosDelegation.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTlsKerberosDelegation.java index 5a9fb0cd1578..9dc8cd3e5428 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTlsKerberosDelegation.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeTlsKerberosDelegation.java @@ -21,6 +21,7 @@ import io.trino.tests.product.launcher.env.Environment; import io.trino.tests.product.launcher.env.EnvironmentConfig; import io.trino.tests.product.launcher.env.EnvironmentProvider; +import io.trino.tests.product.launcher.env.Ipv6; import io.trino.tests.product.launcher.env.ServerPackage; import io.trino.tests.product.launcher.env.Tracing; import io.trino.tests.product.launcher.env.common.HadoopKerberos; @@ -55,6 +56,7 @@ public final class EnvMultinodeTlsKerberosDelegation private final File serverPackage; private final boolean debug; private final boolean tracing; + private final boolean ipv6; @Inject public EnvMultinodeTlsKerberosDelegation( @@ -65,11 +67,13 @@ public EnvMultinodeTlsKerberosDelegation( @ServerPackage File serverPackage, JdkProvider jdkProvider, @Debug boolean debug, - @Tracing boolean tracing) + @Tracing boolean tracing, + @Ipv6 boolean ipv6) { super(ImmutableList.of(standard, hadoopKerberos)); this.configDir = dockerFiles.getDockerFilesHostDirectory("conf/environment/multinode-tls-kerberos-delegation"); this.dockerFiles = requireNonNull(dockerFiles, "dockerFiles is null"); + this.ipv6 = ipv6; String hadoopBaseImage = config.getHadoopBaseImage(); String hadoopImagesVersion = config.getHadoopImagesVersion(); this.trinoDockerImageName = hadoopBaseImage + "-kerberized:" + hadoopImagesVersion; @@ -102,7 +106,7 @@ public void extendEnvironment(Environment.Builder builder) @SuppressWarnings("resource") private DockerContainer createTrinoWorker(String workerName) { - return createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, trinoDockerImageName, workerName) + return createTrinoContainer(dockerFiles, serverPackage, jdkProvider, debug, tracing, ipv6, trinoDockerImageName, workerName) .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withDomainName("docker.cluster")) .withCopyFileToContainer(forHostPath(dockerFiles.getDockerFilesHostPath("conf/environment/multinode-tls-kerberos/config-worker.properties")), CONTAINER_TRINO_CONFIG_PROPERTIES) .withCopyFileToContainer(forHostPath(dockerFiles.getDockerFilesHostPath("conf/environment/multinode-tls-kerberos/hive.properties")), CONTAINER_TRINO_HIVE_PROPERTIES) diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks104.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks104.java index 65d8196523c3..cf204d8d18ab 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks104.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks104.java @@ -18,7 +18,7 @@ import io.trino.tests.product.launcher.env.common.Standard; import io.trino.tests.product.launcher.env.common.TestsEnvironment; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestsEnvironment public class EnvSinglenodeDeltaLakeDatabricks104 @@ -33,6 +33,6 @@ public EnvSinglenodeDeltaLakeDatabricks104(Standard standard, DockerFiles docker @Override String databricksTestJdbcUrl() { - return requireNonNull(System.getenv("DATABRICKS_104_JDBC_URL"), "Environment DATABRICKS_104_JDBC_URL was not set"); + return requireEnv("DATABRICKS_104_JDBC_URL"); } } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks113.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks113.java index d8248cec2d60..54ea27443fbe 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks113.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks113.java @@ -18,7 +18,7 @@ import io.trino.tests.product.launcher.env.common.Standard; import io.trino.tests.product.launcher.env.common.TestsEnvironment; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestsEnvironment public class EnvSinglenodeDeltaLakeDatabricks113 @@ -33,6 +33,6 @@ public EnvSinglenodeDeltaLakeDatabricks113(Standard standard, DockerFiles docker @Override String databricksTestJdbcUrl() { - return requireNonNull(System.getenv("DATABRICKS_113_JDBC_URL"), "Environment DATABRICKS_113_JDBC_URL was not set"); + return requireEnv("DATABRICKS_113_JDBC_URL"); } } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks122.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks122.java index 0435b226bfb1..586fd0f7ee24 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks122.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks122.java @@ -18,7 +18,7 @@ import io.trino.tests.product.launcher.env.common.Standard; import io.trino.tests.product.launcher.env.common.TestsEnvironment; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestsEnvironment public class EnvSinglenodeDeltaLakeDatabricks122 @@ -33,6 +33,6 @@ public EnvSinglenodeDeltaLakeDatabricks122(Standard standard, DockerFiles docker @Override String databricksTestJdbcUrl() { - return requireNonNull(System.getenv("DATABRICKS_122_JDBC_URL"), "Environment DATABRICKS_122_JDBC_URL was not set"); + return requireEnv("DATABRICKS_122_JDBC_URL"); } } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks133.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks133.java index e054cec92822..b1da087a8653 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks133.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks133.java @@ -18,7 +18,7 @@ import io.trino.tests.product.launcher.env.common.Standard; import io.trino.tests.product.launcher.env.common.TestsEnvironment; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestsEnvironment public class EnvSinglenodeDeltaLakeDatabricks133 @@ -33,6 +33,6 @@ public EnvSinglenodeDeltaLakeDatabricks133(Standard standard, DockerFiles docker @Override String databricksTestJdbcUrl() { - return requireNonNull(System.getenv("DATABRICKS_133_JDBC_URL"), "Environment DATABRICKS_133_JDBC_URL was not set"); + return requireEnv("DATABRICKS_133_JDBC_URL"); } } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks143.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks143.java index 06b70041a58b..bec3c502b8b6 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks143.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks143.java @@ -18,7 +18,7 @@ import io.trino.tests.product.launcher.env.common.Standard; import io.trino.tests.product.launcher.env.common.TestsEnvironment; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestsEnvironment public class EnvSinglenodeDeltaLakeDatabricks143 @@ -33,6 +33,6 @@ public EnvSinglenodeDeltaLakeDatabricks143(Standard standard, DockerFiles docker @Override String databricksTestJdbcUrl() { - return requireNonNull(System.getenv("DATABRICKS_143_JDBC_URL"), "Environment DATABRICKS_143_JDBC_URL was not set"); + return requireEnv("DATABRICKS_143_JDBC_URL"); } } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks154.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks154.java index ea427a21c71b..37bb6d41eeb7 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks154.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks154.java @@ -18,7 +18,7 @@ import io.trino.tests.product.launcher.env.common.Standard; import io.trino.tests.product.launcher.env.common.TestsEnvironment; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; @TestsEnvironment public class EnvSinglenodeDeltaLakeDatabricks154 @@ -33,6 +33,6 @@ public EnvSinglenodeDeltaLakeDatabricks154(Standard standard, DockerFiles docker @Override String databricksTestJdbcUrl() { - return requireNonNull(System.getenv("DATABRICKS_154_JDBC_URL"), "Environment DATABRICKS_154_JDBC_URL was not set"); + return requireEnv("DATABRICKS_154_JDBC_URL"); } } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks91.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks91.java deleted file mode 100644 index 0f9b61613109..000000000000 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeDeltaLakeDatabricks91.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.tests.product.launcher.env.environment; - -import com.google.inject.Inject; -import io.trino.tests.product.launcher.docker.DockerFiles; -import io.trino.tests.product.launcher.env.common.Standard; -import io.trino.tests.product.launcher.env.common.TestsEnvironment; - -import static java.util.Objects.requireNonNull; - -@TestsEnvironment -public class EnvSinglenodeDeltaLakeDatabricks91 - extends AbstractSinglenodeDeltaLakeDatabricks -{ - @Inject - public EnvSinglenodeDeltaLakeDatabricks91(Standard standard, DockerFiles dockerFiles) - { - super(standard, dockerFiles); - } - - @Override - String databricksTestJdbcUrl() - { - return requireNonNull(System.getenv("DATABRICKS_91_JDBC_URL"), "Environment DATABRICKS_91_JDBC_URL was not set"); - } -} diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeSparkIcebergNessie.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeSparkIcebergNessie.java index 3b929d278ad7..9c53e019d8f3 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeSparkIcebergNessie.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvSinglenodeSparkIcebergNessie.java @@ -43,7 +43,7 @@ public class EnvSinglenodeSparkIcebergNessie private static final int SPARK_THRIFT_PORT = 10213; private static final int NESSIE_PORT = 19120; - private static final String NESSIE_VERSION = "0.101.1"; + private static final String NESSIE_VERSION = "0.101.3"; private static final String SPARK = "spark"; private final DockerFiles dockerFiles; diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteDeltaLakeDatabricks104.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteDeltaLakeDatabricks104.java index 5ea5c74566fe..decb318190d4 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteDeltaLakeDatabricks104.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteDeltaLakeDatabricks104.java @@ -22,7 +22,8 @@ import java.util.List; import static io.trino.tests.product.TestGroups.CONFIGURED_FEATURES; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS_104; +import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; +import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_104; import static io.trino.tests.product.launcher.suite.SuiteTestRun.testOnEnvironment; public class SuiteDeltaLakeDatabricks104 @@ -33,7 +34,8 @@ public List getTestRuns(EnvironmentConfig config) { return ImmutableList.of( testOnEnvironment(EnvSinglenodeDeltaLakeDatabricks104.class) - .withGroups(CONFIGURED_FEATURES, DELTA_LAKE_DATABRICKS_104) + .withGroups(CONFIGURED_FEATURES, DELTA_LAKE_DATABRICKS) + .withExcludedGroups(DELTA_LAKE_EXCLUDE_104) .withExcludedTests(getExcludedTests()) .build()); } diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteDeltaLakeDatabricks91.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteDeltaLakeDatabricks91.java deleted file mode 100644 index c510d8b2d5ee..000000000000 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteDeltaLakeDatabricks91.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.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.EnvSinglenodeDeltaLakeDatabricks91; -import io.trino.tests.product.launcher.suite.SuiteDeltaLakeDatabricks; -import io.trino.tests.product.launcher.suite.SuiteTestRun; - -import java.util.List; - -import static io.trino.tests.product.TestGroups.CONFIGURED_FEATURES; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_91; -import static io.trino.tests.product.launcher.suite.SuiteTestRun.testOnEnvironment; - -public class SuiteDeltaLakeDatabricks91 - extends SuiteDeltaLakeDatabricks -{ - @Override - public List getTestRuns(EnvironmentConfig config) - { - return ImmutableList.of( - testOnEnvironment(EnvSinglenodeDeltaLakeDatabricks91.class) - .withGroups(CONFIGURED_FEATURES, DELTA_LAKE_DATABRICKS) - .withExcludedGroups(DELTA_LAKE_EXCLUDE_91) - .withExcludedTests(getExcludedTests()) - .build()); - } -} diff --git a/testing/trino-product-tests/pom.xml b/testing/trino-product-tests/pom.xml index 0c39ece9f7be..b524d674de83 100644 --- a/testing/trino-product-tests/pom.xml +++ b/testing/trino-product-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/cli/TestTrinoCli.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/cli/TestTrinoCli.java index 836aba8a92da..715573f254b3 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/cli/TestTrinoCli.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/cli/TestTrinoCli.java @@ -113,10 +113,10 @@ public void shouldDisplayVersion() throws IOException { launchTrinoCli("--version"); - assertThat(trino.readRemainingOutputLines()).containsExactly("Trino CLI " + readPrestoCliVersion()); + assertThat(trino.readRemainingOutputLines()).containsExactly("Trino CLI " + readTrinoCliVersion()); } - private static String readPrestoCliVersion() + private static String readTrinoCliVersion() { try { String version = Resources.toString(Resources.getResource("trino-cli-version.txt"), UTF_8).trim(); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/cli/TestTrinoLdapCli.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/cli/TestTrinoLdapCli.java index e30ea63db6d4..a647e87e2a3f 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/cli/TestTrinoLdapCli.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/cli/TestTrinoLdapCli.java @@ -313,16 +313,16 @@ private void launchTrinoCliWithServerArgument(String... arguments) requireNonNull(ldapServerAddress, "ldapServerAddress is null"); requireNonNull(ldapUserPassword, "ldapUserPassword is null"); - ImmutableList.Builder prestoClientOptions = ImmutableList.builder(); - prestoClientOptions.add( + ImmutableList.Builder trinoClientOptions = ImmutableList.builder(); + trinoClientOptions.add( "--server", ldapServerAddress, "--truststore-path", ldapTruststorePath, "--truststore-password", ldapTruststorePassword, "--user", ldapUserName, "--password"); - prestoClientOptions.add(arguments); - ProcessBuilder processBuilder = getProcessBuilder(prestoClientOptions.build()); + trinoClientOptions.add(arguments); + ProcessBuilder processBuilder = getProcessBuilder(trinoClientOptions.build()); processBuilder.environment().put("TRINO_PASSWORD", ldapUserPassword); trino = new TrinoCliProcess(processBuilder.start()); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/BaseTestDeltaLakeMinioReads.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/BaseTestDeltaLakeMinioReads.java index 351ea5a17a1e..88df1a82af34 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/BaseTestDeltaLakeMinioReads.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/BaseTestDeltaLakeMinioReads.java @@ -79,7 +79,7 @@ public void testReadRegionTable() format("SELECT count(name) FROM delta.default.\"%s\"", tableName))) .containsOnly(row(5L)); - assertNotificationsCount(NOTIFICATIONS_TABLE, OBJECT_ACCESSED_HEAD, tableName + "/_delta_log/00000000000000000000.json", 0); + assertNotificationsCount(NOTIFICATIONS_TABLE, OBJECT_ACCESSED_HEAD, tableName + "/_delta_log/00000000000000000000.json", 1); assertNotificationsCount(NOTIFICATIONS_TABLE, OBJECT_ACCESSED_GET, tableName + "/_delta_log/00000000000000000000.json", 1); onTrino().executeQuery(format("DROP TABLE delta.default.\"%s\"", tableName)); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/BaseTestDeltaLakeS3Storage.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/BaseTestDeltaLakeS3Storage.java index a0ec14e63776..5feaca425845 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/BaseTestDeltaLakeS3Storage.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/BaseTestDeltaLakeS3Storage.java @@ -16,7 +16,7 @@ import io.trino.tempto.BeforeMethodWithContext; import io.trino.tempto.ProductTest; -import static java.util.Objects.requireNonNull; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; public abstract class BaseTestDeltaLakeS3Storage extends ProductTest @@ -26,6 +26,6 @@ public abstract class BaseTestDeltaLakeS3Storage @BeforeMethodWithContext public void setUp() { - bucketName = requireNonNull(System.getenv("S3_BUCKET"), "Environment variable not set: S3_BUCKET"); + bucketName = requireEnv("S3_BUCKET"); } } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/S3ClientFactory.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/S3ClientFactory.java index c81171488413..ca372999c91a 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/S3ClientFactory.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/S3ClientFactory.java @@ -25,9 +25,9 @@ import com.amazonaws.services.s3.model.Region; import io.trino.testing.minio.MinioClient; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.minio.MinioClient.DEFAULT_MINIO_ACCESS_KEY; import static io.trino.testing.minio.MinioClient.DEFAULT_MINIO_SECRET_KEY; -import static java.util.Objects.requireNonNull; final class S3ClientFactory { @@ -45,7 +45,7 @@ public AmazonS3 createS3Client(String serverType) private AmazonS3 createAwsS3Client() { - String region = requireNonNull(System.getenv("AWS_REGION"), "AWS_REGION is null"); + String region = requireEnv("AWS_REGION"); return AmazonS3Client.builder().withRegion(region).build(); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDatabricksWithGlueMetastoreCleanUp.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDatabricksWithGlueMetastoreCleanUp.java index bb600aebc6ef..70fea807582c 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDatabricksWithGlueMetastoreCleanUp.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDatabricksWithGlueMetastoreCleanUp.java @@ -34,7 +34,7 @@ import java.util.stream.Collectors; import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static io.trino.plugin.hive.metastore.glue.v1.converter.GlueToTrinoConverter.getTableType; +import static io.trino.plugin.hive.metastore.glue.v1.GlueToTrinoConverter.getTableType; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; import static io.trino.tests.product.deltalake.util.DeltaLakeTestUtils.DATABRICKS_COMMUNICATION_FAILURE_ISSUE; diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAlluxioCaching.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAlluxioCaching.java index ce75707232ec..ee3e8ee772f3 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAlluxioCaching.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAlluxioCaching.java @@ -19,12 +19,12 @@ import io.trino.tests.product.utils.CachingTestUtils.CacheStats; import org.testng.annotations.Test; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.TestGroups.DELTA_LAKE_ALLUXIO_CACHING; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; import static io.trino.tests.product.utils.CachingTestUtils.getCacheStats; import static io.trino.tests.product.utils.QueryAssertions.assertEventually; import static io.trino.tests.product.utils.QueryExecutors.onTrino; -import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; @@ -36,7 +36,7 @@ public class TestDeltaLakeAlluxioCaching @BeforeMethodWithContext public void setUp() { - bucketName = requireNonNull(System.getenv("S3_BUCKET"), "Environment variable not set: S3_BUCKET"); + bucketName = requireEnv("S3_BUCKET"); } @Test(groups = {DELTA_LAKE_ALLUXIO_CACHING, PROFILE_SPECIFIC_TESTS}) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAlterTableCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAlterTableCompatibility.java index c904549cc8ad..bc8d7edd1f24 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAlterTableCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAlterTableCompatibility.java @@ -25,7 +25,7 @@ import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_91; +import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_104; 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.util.DatabricksVersion.DATABRICKS_143_RUNTIME_VERSION; @@ -244,7 +244,7 @@ public void testTrinoPreservesReaderAndWriterVersions() } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_EXCLUDE_91, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_EXCLUDE_104, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testTrinoPreservesTableFeature() { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAzure.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAzure.java index 97c8816c9465..115dbe334887 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAzure.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeAzure.java @@ -19,10 +19,10 @@ import io.trino.tests.product.BaseTestTableFormats; import org.testng.annotations.Test; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.TestGroups.DELTA_LAKE_AZURE; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; public class TestDeltaLakeAzure extends BaseTestTableFormats @@ -35,8 +35,8 @@ public class TestDeltaLakeAzure @BeforeMethodWithContext public void setUp() { - String container = requireNonNull(System.getenv("ABFS_CONTAINER"), "Environment variable not set: ABFS_CONTAINER"); - String account = requireNonNull(System.getenv("ABFS_ACCOUNT"), "Environment variable not set: ABFS_ACCOUNT"); + String container = requireEnv("ABFS_CONTAINER"); + String account = requireEnv("ABFS_ACCOUNT"); schemaLocation = format("abfs://%s@%s.dfs.core.windows.net/%s", container, account, schema); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCaseInsensitiveMapping.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCaseInsensitiveMapping.java index 41049876761c..30971019b12a 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCaseInsensitiveMapping.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCaseInsensitiveMapping.java @@ -24,12 +24,11 @@ import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -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_DATABRICKS_133; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS_143; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_91; +import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_104; 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.util.DatabricksVersion.DATABRICKS_143_RUNTIME_VERSION; @@ -76,7 +75,7 @@ public void testNonLowercaseColumnNames() } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testNonLowercaseFieldNames() { @@ -192,7 +191,7 @@ public void testGeneratedColumnWithNonLowerCaseColumnName() } // Exclude 10.4 because it throws MISSING_COLUMN when executing INSERT statement - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_EXCLUDE_104, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testIdentityColumnWithNonLowerCaseColumnName() { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeChangeDataFeedCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeChangeDataFeedCompatibility.java index f4633503d194..fca2c054dbcf 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeChangeDataFeedCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeChangeDataFeedCompatibility.java @@ -31,12 +31,11 @@ import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -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_DATABRICKS_133; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS_143; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_91; +import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_104; 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.util.DeltaLakeTestUtils.DATABRICKS_COMMUNICATION_FAILURE_ISSUE; @@ -64,7 +63,7 @@ public void setup() s3Client = new S3ClientFactory().createS3Client(s3ServerType); } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingModeDataProvider") + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingModeDataProvider") @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testUpdateTableWithCdf(String columnMappingMode) { @@ -100,7 +99,7 @@ public void testUpdateTableWithCdf(String columnMappingMode) } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_104, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testUpdateTableWithChangeDataFeedWriterFeature() { @@ -201,7 +200,7 @@ public void testUpdateCdfTableWithNonLowercaseColumn(String columnMappingMode) } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingModeDataProvider") + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingModeDataProvider") @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testUpdatePartitionedTableWithCdf(String columnMappingMode) { @@ -387,7 +386,7 @@ public void testUpdateTableWithCdfEnabledAfterTableIsAlreadyCreated() } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingModeDataProvider") + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingModeDataProvider") @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testDeleteFromTableWithCdf(String columnMappingMode) { @@ -533,7 +532,7 @@ public void testMergeDeleteIntoTableWithCdfEnabled(String columnMappingMode) } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testMergeMixedDeleteAndUpdateIntoTableWithCdfEnabled() { 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 b7a88824e179..530a409e2402 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 @@ -38,7 +38,6 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -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_DATABRICKS_133; @@ -279,7 +278,7 @@ private void trinoUsesCheckpointInterval(String deltaTableProperties) } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testDatabricksUsesCheckpointInterval() { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCloneTableCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCloneTableCompatibility.java index e5e7428f954c..d773c66a6547 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCloneTableCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCloneTableCompatibility.java @@ -28,7 +28,7 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_91; +import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_104; 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.util.DeltaLakeTestUtils.DATABRICKS_COMMUNICATION_FAILURE_ISSUE; @@ -241,7 +241,7 @@ public void testReadFromSchemaChangedShallowCloneTable() testReadSchemaChangedCloneTable("SHALLOW", false); } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_EXCLUDE_104, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testReadFromSchemaChangedDeepCloneTable() { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeColumnMappingMode.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeColumnMappingMode.java index 5b8c89476177..965eda2d4df6 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeColumnMappingMode.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeColumnMappingMode.java @@ -27,12 +27,10 @@ import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -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_DATABRICKS_133; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS_143; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_91; 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.util.DeltaLakeTestUtils.DATABRICKS_COMMUNICATION_FAILURE_ISSUE; @@ -56,7 +54,7 @@ public class TestDeltaLakeColumnMappingMode extends BaseTestDeltaLakeS3Storage { - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testColumnMappingModeNone() { @@ -235,7 +233,7 @@ private void testColumnMappingModeReaderAndWriterVersion(Consumer create onTrino().executeQuery("DROP TABLE delta.default." + tableName); } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingDataProvider") + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingDataProvider") @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testTrinoColumnMappingMode(String mode) { @@ -249,7 +247,7 @@ public void testTrinoColumnMappingMode(String mode) ")")); } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingDataProvider") + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingDataProvider") @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testDeltaColumnMappingMode(String mode) { @@ -666,7 +664,7 @@ public void testShowStatsOnPartitionedForColumnMappingModeId() } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingDataProvider") + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingDataProvider") @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testProjectionPushdownDmlWithColumnMappingMode(String mode) { 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 73adc6255973..a6845c3492f5 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 @@ -25,7 +25,6 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -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_DATABRICKS_133; @@ -44,9 +43,9 @@ public class TestDeltaLakeCreateTableAsSelectCompatibility extends BaseTestDeltaLakeS3Storage { - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) - public void testPrestoTypesWithDatabricks() + public void testTrinoTypesWithDatabricks() { String tableName = "test_dl_ctas_" + randomNameSuffix(); @@ -64,8 +63,8 @@ public void testPrestoTypesWithDatabricks() .containsOnly(row(7)); QueryResult databricksResult = onDelta().executeQuery(format("SELECT * FROM default.%s", tableName)); - QueryResult prestoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\"", tableName)); - assertThat(databricksResult).containsOnly(prestoResult.rows().stream() + QueryResult trinoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\"", tableName)); + assertThat(databricksResult).containsOnly(trinoResult.rows().stream() .map(QueryAssert.Row::new) .collect(toImmutableList())); } @@ -76,7 +75,7 @@ public void testPrestoTypesWithDatabricks() @Test(groups = {DELTA_LAKE_DATABRICKS, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) - public void testPrestoTimestampsWithDatabricks() + public void testTrinoTimestampsWithDatabricks() { String tableName = "test_dl_ctas_timestamps_" + randomNameSuffix(); @@ -91,8 +90,8 @@ public void testPrestoTimestampsWithDatabricks() .containsOnly(row(4)); QueryResult databricksResult = onDelta().executeQuery("SELECT id, date_format(timestamp_in_utc, \"yyyy-MM-dd HH:mm:ss.SSS\"), date_format(timestamp_in_new_york, \"yyyy-MM-dd HH:mm:ss.SSS\"), date_format(timestamp_in_warsaw, \"yyyy-MM-dd HH:mm:ss.SSS\") FROM default." + tableName); - QueryResult prestoResult = onTrino().executeQuery("SELECT id, format('%1$tF %1$tT.%1$tL', timestamp_in_utc), format('%1$tF %1$tT.%1$tL', timestamp_in_new_york), format('%1$tF %1$tT.%1$tL', timestamp_in_warsaw) FROM delta.default.\"" + tableName + "\""); - assertThat(databricksResult).containsOnly(prestoResult.rows().stream() + QueryResult trinoResult = onTrino().executeQuery("SELECT id, format('%1$tF %1$tT.%1$tL', timestamp_in_utc), format('%1$tF %1$tT.%1$tL', timestamp_in_new_york), format('%1$tF %1$tT.%1$tL', timestamp_in_warsaw) FROM delta.default.\"" + tableName + "\""); + assertThat(databricksResult).containsOnly(trinoResult.rows().stream() .map(QueryAssert.Row::new) .collect(toImmutableList())); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksCreateTableCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksCreateTableCompatibility.java index a6406a1fef84..09dad521a2d9 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksCreateTableCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksCreateTableCompatibility.java @@ -401,6 +401,13 @@ public void testCreateTableWithAllPartitionColumns() private String getDatabricksDefaultTableProperties() { + if (databricksRuntimeVersion.equals(DATABRICKS_104_RUNTIME_VERSION)) { + return "TBLPROPERTIES (\n" + + " 'Type' = 'EXTERNAL',\n" + + " 'delta.enableDeletionVectors' = 'false',\n" + + " 'delta.minReaderVersion' = '1',\n" + + " 'delta.minWriterVersion' = '2')\n"; + } if (databricksRuntimeVersion.isAtLeast(DATABRICKS_113_RUNTIME_VERSION)) { return "TBLPROPERTIES (\n" + " 'delta.enableDeletionVectors' = 'false',\n" + diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksUnityCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksUnityCompatibility.java index 5c9cec3d33ef..8dbb1fdf9e32 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksUnityCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksUnityCompatibility.java @@ -25,6 +25,7 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DATABRICKS_UNITY_HTTP_HMS; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; @@ -34,7 +35,6 @@ import static io.trino.tests.product.utils.QueryExecutors.onTrino; import static java.lang.String.format; import static java.util.Locale.ENGLISH; -import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; public class TestDeltaLakeDatabricksUnityCompatibility @@ -47,8 +47,8 @@ public class TestDeltaLakeDatabricksUnityCompatibility @BeforeMethodWithContext public void setUp() { - unityCatalogName = requireNonNull(System.getenv("DATABRICKS_UNITY_CATALOG_NAME"), "Environment variable not set: DATABRICKS_UNITY_CATALOG_NAME"); - externalLocationPath = requireNonNull(System.getenv("DATABRICKS_UNITY_EXTERNAL_LOCATION"), "Environment variable not set: DATABRICKS_UNITY_EXTERNAL_LOCATION"); + unityCatalogName = requireEnv("DATABRICKS_UNITY_CATALOG_NAME"); + externalLocationPath = requireEnv("DATABRICKS_UNITY_EXTERNAL_LOCATION"); onDelta().executeQuery(format("CREATE SCHEMA %s.%s", unityCatalogName, schemaName)); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDeleteCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDeleteCompatibility.java index b022ad716b42..ef24c834f3a8 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDeleteCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDeleteCompatibility.java @@ -31,7 +31,7 @@ import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_91; +import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_104; 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.util.DeltaLakeTestUtils.DATABRICKS_COMMUNICATION_FAILURE_ISSUE; @@ -208,7 +208,7 @@ public void testTrinoDeletionVectors() } // Databricks 12.1 and OSS Delta 2.4.0 added support for deletion vectors - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingModeDataProvider") + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_104, PROFILE_SPECIFIC_TESTS}, dataProvider = "columnMappingModeDataProvider") @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testDeletionVectors(String mode) { @@ -495,7 +495,7 @@ public void testDeletionVectorsAcrossAddFile(boolean partitioned) } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_91, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_104, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testDeletionVectorsTruncateTable() { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeIdentityColumnCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeIdentityColumnCompatibility.java index 4bf5648133a2..a0cf50cd03e4 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeIdentityColumnCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeIdentityColumnCompatibility.java @@ -21,7 +21,6 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; import static io.trino.testing.TestingNames.randomNameSuffix; -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.PROFILE_SPECIFIC_TESTS; import static io.trino.tests.product.deltalake.util.DeltaLakeTestUtils.DATABRICKS_COMMUNICATION_FAILURE_ISSUE; @@ -38,7 +37,7 @@ public class TestDeltaLakeIdentityColumnCompatibility extends BaseTestDeltaLakeS3Storage { - @Test(groups = {DELTA_LAKE_DATABRICKS_104, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS_113, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testIdentityColumn() { @@ -153,7 +152,7 @@ public void testDropIdentityColumn(String mode) } } - @Test(groups = {DELTA_LAKE_DATABRICKS_104, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS_113, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testVacuumProcedureWithIdentityColumn() { @@ -184,7 +183,7 @@ public void testVacuumProcedureWithIdentityColumn() } } - @Test(groups = {DELTA_LAKE_DATABRICKS_104, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS_113, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testIdentityColumnCheckpointInterval() { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeInsertCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeInsertCompatibility.java index 61c6a6fb16ce..b7290e0839b0 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeInsertCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeInsertCompatibility.java @@ -31,7 +31,6 @@ import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -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_DATABRICKS_133; @@ -59,7 +58,7 @@ public void setup() databricksRuntimeVersion = getDatabricksRuntimeVersion(); } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testInsertCompatibility() { @@ -95,7 +94,7 @@ public void testInsertCompatibility() } } - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testPartitionedInsertCompatibility() { @@ -436,8 +435,8 @@ public void verifyCompressionCodecsDataProvider() assertThat(onTrino().executeQuery("SHOW SESSION LIKE 'delta.compression_codec'")) .containsOnly(row( "delta.compression_codec", - "SNAPPY", - "SNAPPY", + "ZSTD", + "ZSTD", "varchar", "Compression codec to use when writing new data files. Possible values: " + Stream.of(compressionCodecs()) 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 fa7f14eaba86..6e92f6953bf4 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 @@ -35,7 +35,7 @@ public void testJmxTablesExposedByDeltaLakeConnectorBackedByGlueMetastore() { assertThat(onTrino().executeQuery("SHOW TABLES IN jmx.current LIKE '%name=delta%'")).containsOnly( row("io.trino.filesystem.s3:name=delta,type=s3filesystemstats"), - row("io.trino.plugin.hive.metastore.cache:name=delta,type=cachinghivemetastore"), + row("io.trino.metastore.cache:name=delta,type=cachinghivemetastore"), row("io.trino.plugin.hive.metastore.glue:name=delta,type=gluehivemetastore"), row("io.trino.plugin.hive.metastore.glue:name=delta,type=gluemetastorestats"), row("io.trino.plugin.base.metrics:catalog=delta,name=delta,type=fileformatdatasourcestats"), @@ -48,7 +48,7 @@ public void testJmxTablesExposedByDeltaLakeConnectorBackedByThriftMetastore() { assertThat(onTrino().executeQuery("SHOW TABLES IN jmx.current LIKE '%name=delta%'")).containsOnly( row("io.trino.filesystem.s3:name=delta,type=s3filesystemstats"), - row("io.trino.plugin.hive.metastore.cache:name=delta,type=cachinghivemetastore"), + row("io.trino.metastore.cache:name=delta,type=cachinghivemetastore"), row("io.trino.plugin.hive.metastore.thrift:name=delta,type=thrifthivemetastore"), row("io.trino.plugin.hive.metastore.thrift:name=delta,type=thriftmetastorestats"), row("io.trino.plugin.base.metrics:catalog=delta,name=delta,type=fileformatdatasourcestats"), diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeSelectCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeSelectCompatibility.java index e14e757ac7d6..3a5a65f57132 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeSelectCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeSelectCompatibility.java @@ -24,7 +24,6 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -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_DATABRICKS_133; @@ -42,7 +41,7 @@ public class TestDeltaLakeSelectCompatibility extends BaseTestDeltaLakeS3Storage { - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, DELTA_LAKE_DATABRICKS_133, DELTA_LAKE_DATABRICKS_143, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testPartitionedSelectSpecialCharacters() { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeSystemTableCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeSystemTableCompatibility.java index 723319275880..63417308ebb7 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeSystemTableCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeSystemTableCompatibility.java @@ -24,7 +24,7 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_91; +import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_104; 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.util.DeltaLakeTestUtils.DATABRICKS_COMMUNICATION_FAILURE_ISSUE; @@ -38,7 +38,7 @@ public class TestDeltaLakeSystemTableCompatibility extends BaseTestDeltaLakeS3Storage { - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_EXCLUDE_91, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_EXCLUDE_104, DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testTablePropertiesCaseSensitivity() { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeUpdateCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeUpdateCompatibility.java index e1a187982d0b..918507de550e 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeUpdateCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeUpdateCompatibility.java @@ -24,7 +24,6 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -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.PROFILE_SPECIFIC_TESTS; @@ -39,7 +38,7 @@ public class TestDeltaLakeUpdateCompatibility extends BaseTestDeltaLakeS3Storage { - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_104, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_DATABRICKS_113, DELTA_LAKE_DATABRICKS_122, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testUpdatesFromDatabricks() { @@ -55,23 +54,23 @@ public void testUpdatesFromDatabricks() try { QueryResult databricksResult = onDelta().executeQuery(format("SELECT * FROM default.%s ORDER BY id", tableName)); - QueryResult prestoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\" ORDER BY id", tableName)); - assertThat(databricksResult).containsExactlyInOrder(toRows(prestoResult)); + QueryResult trinoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\" ORDER BY id", tableName)); + assertThat(databricksResult).containsExactlyInOrder(toRows(trinoResult)); onDelta().executeQuery(format("UPDATE default.%s SET value = 'France' WHERE id = 2", tableName)); databricksResult = onDelta().executeQuery(format("SELECT * FROM default.%s ORDER BY id", tableName)); - prestoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\" ORDER BY id", tableName)); - assertThat(databricksResult).containsExactlyInOrder(toRows(prestoResult)); + trinoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\" ORDER BY id", tableName)); + assertThat(databricksResult).containsExactlyInOrder(toRows(trinoResult)); onDelta().executeQuery(format("UPDATE default.%s SET value = 'Spain' WHERE id = 2", tableName)); databricksResult = onDelta().executeQuery(format("SELECT * FROM default.%s ORDER BY id", tableName)); - prestoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\" ORDER BY id", tableName)); - assertThat(databricksResult).containsExactlyInOrder(toRows(prestoResult)); + trinoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\" ORDER BY id", tableName)); + assertThat(databricksResult).containsExactlyInOrder(toRows(trinoResult)); onDelta().executeQuery(format("UPDATE default.%s SET value = 'Portugal' WHERE id = 2", tableName)); databricksResult = onDelta().executeQuery(format("SELECT * FROM default.%s ORDER BY id", tableName)); - prestoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\" ORDER BY id", tableName)); - assertThat(databricksResult).containsExactlyInOrder(toRows(prestoResult)); + trinoResult = onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s\" ORDER BY id", tableName)); + assertThat(databricksResult).containsExactlyInOrder(toRows(trinoResult)); } finally { dropDeltaTableWithRetry("default." + tableName); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeWriteDatabricksCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeWriteDatabricksCompatibility.java index bbf752da65df..6e08f9a20a9d 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeWriteDatabricksCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeWriteDatabricksCompatibility.java @@ -35,7 +35,7 @@ import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; -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_OSS; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; import static io.trino.tests.product.deltalake.util.DatabricksVersion.DATABRICKS_122_RUNTIME_VERSION; @@ -323,7 +323,7 @@ public void testInsertingIntoDatabricksTableWithAddedNotNullConstraint() } } - @Test(groups = {DELTA_LAKE_DATABRICKS_104, PROFILE_SPECIFIC_TESTS}) + @Test(groups = {DELTA_LAKE_DATABRICKS_113, PROFILE_SPECIFIC_TESTS}) @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) public void testTrinoVacuumRemoveChangeDataFeedFiles() { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/AbstractTestHiveViews.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/AbstractTestHiveViews.java index 88a8c224f943..73173dc63053 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/AbstractTestHiveViews.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/AbstractTestHiveViews.java @@ -81,7 +81,7 @@ public void testArrayIndexingInView() { onHive().executeQuery("DROP TABLE IF EXISTS test_hive_view_array_index_table"); onHive().executeQuery("CREATE TABLE test_hive_view_array_index_table(an_index int, an_array array)"); - onHive().executeQuery("INSERT INTO TABLE test_hive_view_array_index_table SELECT 1, array('presto','hive') FROM nation WHERE n_nationkey = 1"); + onHive().executeQuery("INSERT INTO TABLE test_hive_view_array_index_table SELECT 1, array('trino','hive') FROM nation WHERE n_nationkey = 1"); // literal array index onHive().executeQuery("DROP VIEW IF EXISTS test_hive_view_array_index_view"); @@ -761,7 +761,7 @@ public void testRunAsInvoker() protected static void assertViewQuery(String query, Consumer assertion) { - // Ensure Hive and Presto view compatibility by comparing the results + // Ensure Hive and Trino view compatibility by comparing the results assertion.accept(assertThat(onHive().executeQuery(query))); assertion.accept(assertThat(onTrino().executeQuery(query))); } 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 9745a3dc051d..7f6d3cc1b18d 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 @@ -203,17 +203,17 @@ protected void doTestHiveCoercion(HiveTableDefinition tableDefinition) Function>> expected = engine -> expectedValuesForEngineProvider(engine, tableName, booleanToVarcharVal); // For Trino, remove unsupported columns - List prestoReadColumns = removeUnsupportedColumnsForTrino(allColumns, tableName); - Map> expectedPrestoResults = expected.apply(Engine.TRINO); + List trinoReadColumns = removeUnsupportedColumnsForTrino(allColumns, tableName); + Map> expectedTrinoResults = expected.apply(Engine.TRINO); // In case of unpartitioned tables we don't support all the column coercion thereby making this assertion conditional if (expectedExceptionsWithTrinoContext().isEmpty()) { - assertThat(ImmutableSet.copyOf(prestoReadColumns)).isEqualTo(expectedPrestoResults.keySet()); + assertThat(ImmutableSet.copyOf(trinoReadColumns)).isEqualTo(expectedTrinoResults.keySet()); } - String prestoSelectQuery = format("SELECT %s FROM %s", String.join(", ", prestoReadColumns), tableName); - assertQueryResults(Engine.TRINO, prestoSelectQuery, expectedPrestoResults, prestoReadColumns, 2); + String trinoSelectQuery = format("SELECT %s FROM %s", String.join(", ", trinoReadColumns), tableName); + assertQueryResults(Engine.TRINO, trinoSelectQuery, expectedTrinoResults, trinoReadColumns, 2); // Additional assertions for VARBINARY coercion - if (prestoReadColumns.contains("binary_to_string")) { + if (trinoReadColumns.contains("binary_to_string")) { List hexRepresentedValue = ImmutableList.of("58EFBFBDEFBFBDEFBFBDEFBFBD", "58EFBFBDEFBFBDEFBFBDEFBFBD58"); if (tableName.toLowerCase(ENGLISH).contains("orc")) { diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAbfsSyncPartitionMetadata.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAbfsSyncPartitionMetadata.java index 20b1a0339730..37ef181ba5c6 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAbfsSyncPartitionMetadata.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAbfsSyncPartitionMetadata.java @@ -21,13 +21,13 @@ import org.testng.annotations.Test; import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.TestGroups.AZURE; import static io.trino.tests.product.utils.HadoopTestUtils.RETRYABLE_FAILURES_ISSUES; import static io.trino.tests.product.utils.HadoopTestUtils.RETRYABLE_FAILURES_MATCH; import static io.trino.tests.product.utils.QueryExecutors.onHive; import static io.trino.tests.product.utils.QueryExecutors.onTrino; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; import static org.apache.parquet.Strings.isNullOrEmpty; public class TestAbfsSyncPartitionMetadata @@ -53,8 +53,8 @@ public void tearDown() @Override protected String schemaLocation() { - String container = requireNonNull(System.getenv("ABFS_CONTAINER"), "Environment variable not set: ABFS_CONTAINER"); - String account = requireNonNull(System.getenv("ABFS_ACCOUNT"), "Environment variable not set: ABFS_ACCOUNT"); + String container = requireEnv("ABFS_CONTAINER"); + String account = requireEnv("ABFS_ACCOUNT"); return format("abfs://%s@%s.dfs.core.windows.net/%s", container, account, schema); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAvroSchemaUrl.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAvroSchemaUrl.java index 94368abc6094..e5aafaccc4be 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAvroSchemaUrl.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAvroSchemaUrl.java @@ -76,7 +76,7 @@ private void saveResourceOnHdfs(String resource, String location) public Object[][] avroSchemaLocations() { return new Object[][] { - {"file:///docker/trino-product-tests/avro/original_schema.avsc"}, // mounted in hadoop and presto containers + {"file:///docker/trino-product-tests/avro/original_schema.avsc"}, // mounted in hadoop and trino containers {"hdfs://hadoop-master:9000/user/hive/warehouse/TestAvroSchemaUrl/schemas/original_schema.avsc"}, {"hdfs:///user/hive/warehouse/TestAvroSchemaUrl/schemas/original_schema.avsc"}, {"/user/hive/warehouse/TestAvroSchemaUrl/schemas/original_schema.avsc"}, // `avro.schema.url` can actually be path on HDFS (not URL) @@ -149,16 +149,16 @@ public void testAvroSchemaUrlInSerdeProperties() @Test(dataProvider = "avroSchemaLocations", groups = STORAGE_FORMATS) @Flaky(issue = RETRYABLE_FAILURES_ISSUES, match = RETRYABLE_FAILURES_MATCH) - public void testPrestoCreatedTable(String schemaLocation) + public void testTrinoCreatedTable(String schemaLocation) { - onTrino().executeQuery("DROP TABLE IF EXISTS test_avro_schema_url_presto"); - onTrino().executeQuery(format("CREATE TABLE test_avro_schema_url_presto (dummy_col VARCHAR) WITH (format='AVRO', avro_schema_url='%s')", schemaLocation)); - onTrino().executeQuery("INSERT INTO test_avro_schema_url_presto VALUES ('some text', 123042)"); + onTrino().executeQuery("DROP TABLE IF EXISTS test_avro_schema_url_trino"); + onTrino().executeQuery(format("CREATE TABLE test_avro_schema_url_trino (dummy_col VARCHAR) WITH (format='AVRO', avro_schema_url='%s')", schemaLocation)); + onTrino().executeQuery("INSERT INTO test_avro_schema_url_trino VALUES ('some text', 123042)"); - assertThat(onHive().executeQuery("SELECT * FROM test_avro_schema_url_presto")).containsExactlyInOrder(row("some text", 123042)); - assertThat(onTrino().executeQuery("SELECT * FROM test_avro_schema_url_presto")).containsExactlyInOrder(row("some text", 123042)); + assertThat(onHive().executeQuery("SELECT * FROM test_avro_schema_url_trino")).containsExactlyInOrder(row("some text", 123042)); + assertThat(onTrino().executeQuery("SELECT * FROM test_avro_schema_url_trino")).containsExactlyInOrder(row("some text", 123042)); - onTrino().executeQuery("DROP TABLE test_avro_schema_url_presto"); + onTrino().executeQuery("DROP TABLE test_avro_schema_url_trino"); } @Test(groups = STORAGE_FORMATS) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAzureBlobFileSystem.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAzureBlobFileSystem.java index e816b3428542..090da1d5d554 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAzureBlobFileSystem.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestAzureBlobFileSystem.java @@ -26,6 +26,7 @@ import java.util.List; import static io.trino.tempto.assertions.QueryAssert.Row.row; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.AZURE; import static io.trino.tests.product.utils.HadoopTestUtils.RETRYABLE_FAILURES_ISSUES; @@ -33,7 +34,6 @@ import static io.trino.tests.product.utils.QueryExecutors.onHive; import static io.trino.tests.product.utils.QueryExecutors.onTrino; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; public class TestAzureBlobFileSystem @@ -47,8 +47,8 @@ public class TestAzureBlobFileSystem @BeforeMethodWithContext public void setUp() { - String container = requireNonNull(System.getenv("ABFS_CONTAINER"), "Environment variable not set: ABFS_CONTAINER"); - String account = requireNonNull(System.getenv("ABFS_ACCOUNT"), "Environment variable not set: ABFS_ACCOUNT"); + String container = requireEnv("ABFS_CONTAINER"); + String account = requireEnv("ABFS_ACCOUNT"); schemaLocation = format("abfs://%s@%s.dfs.core.windows.net/%s", container, account, schema); onHive().executeQuery("dfs -rm -f -r " + schemaLocation); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestGrantRevoke.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestGrantRevoke.java index 10d875656afb..098f74a7d2ac 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestGrantRevoke.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestGrantRevoke.java @@ -58,7 +58,7 @@ public class TestGrantRevoke * Pre-requisites for the tests in this class: * * (1) hive.properties file should have this property set: hive.security=sql-standard - * (2) tempto-configuration.yaml file should have definitions for the following connections to Presto server: + * (2) tempto-configuration.yaml file should have definitions for the following connections to Trino server: * - "alice@trino" that has "jdbc_user: alice" * - "bob@trino" that has "jdbc_user: bob" * - "charlie@trino" that has "jdbc_user: charlie" diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveAzure.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveAzure.java index b8586917cc91..0b163e47e2af 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveAzure.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveAzure.java @@ -19,10 +19,10 @@ import io.trino.tests.product.BaseTestTableFormats; import org.testng.annotations.Test; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.TestGroups.AZURE; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; public class TestHiveAzure extends BaseTestTableFormats @@ -35,8 +35,8 @@ public class TestHiveAzure @BeforeMethodWithContext public void setUp() { - String container = requireNonNull(System.getenv("ABFS_CONTAINER"), "Environment variable not set: ABFS_CONTAINER"); - String account = requireNonNull(System.getenv("ABFS_ACCOUNT"), "Environment variable not set: ABFS_ACCOUNT"); + String container = requireEnv("ABFS_CONTAINER"); + String account = requireEnv("ABFS_ACCOUNT"); schemaLocation = format("abfs://%s@%s.dfs.core.windows.net/%s", container, account, schema); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveBasicTableStatistics.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveBasicTableStatistics.java index 9cad3fe41ec4..f8adc77c7480 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveBasicTableStatistics.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveBasicTableStatistics.java @@ -43,7 +43,7 @@ public class TestHiveBasicTableStatistics @Test public void testCreateUnpartitioned() { - String tableName = "test_basic_statistics_unpartitioned_ctas_presto"; + String tableName = "test_basic_statistics_unpartitioned_ctas_trino"; onTrino().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); onTrino().executeQuery(format("CREATE TABLE %s AS SELECT * FROM nation", tableName)); @@ -61,7 +61,7 @@ public void testCreateUnpartitioned() @Test public void testCreateExternalUnpartitioned() { - String tableName = "test_basic_statistics_external_unpartitioned_presto"; + String tableName = "test_basic_statistics_external_unpartitioned_trino"; onTrino().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); @@ -87,7 +87,7 @@ public void testCreateExternalUnpartitioned() @Test public void testCreateTableWithNoData() { - String tableName = "test_basic_statistics_unpartitioned_ctas_presto_with_no_data"; + String tableName = "test_basic_statistics_unpartitioned_ctas_trino_with_no_data"; onTrino().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); onTrino().executeQuery(format("CREATE TABLE %s AS SELECT * FROM nation WITH NO DATA", tableName)); @@ -104,7 +104,7 @@ public void testCreateTableWithNoData() @Test public void testInsertUnpartitioned() { - String tableName = "test_basic_statistics_unpartitioned_insert_presto"; + String tableName = "test_basic_statistics_unpartitioned_insert_trino"; onTrino().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); onTrino().executeQuery(format("" + @@ -140,7 +140,7 @@ public void testInsertUnpartitioned() @Test public void testCreatePartitioned() { - String tableName = "test_basic_statistics_partitioned_ctas_presto"; + String tableName = "test_basic_statistics_partitioned_ctas_trino"; onTrino().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); onTrino().executeQuery(format("" + @@ -252,7 +252,7 @@ public void testAnalyzeUnpartitioned() @Test public void testInsertPartitioned() { - String tableName = "test_basic_statistics_partitioned_insert_presto"; + String tableName = "test_basic_statistics_partitioned_insert_trino"; onTrino().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); onTrino().executeQuery(format("" + @@ -294,7 +294,7 @@ public void testInsertPartitioned() @Flaky(issue = RETRYABLE_FAILURES_ISSUES, match = RETRYABLE_FAILURES_MATCH) public void testInsertBucketed() { - String tableName = "test_basic_statistics_bucketed_insert_presto"; + String tableName = "test_basic_statistics_bucketed_insert_trino"; onTrino().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); onTrino().executeQuery(format("" + @@ -333,7 +333,7 @@ public void testInsertBucketed() @Test public void testInsertBucketedPartitioned() { - String tableName = "test_basic_statistics_bucketed_partitioned_insert_presto"; + String tableName = "test_basic_statistics_bucketed_partitioned_insert_trino"; onTrino().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); onTrino().executeQuery(format("" + diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveDatabricksUnityCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveDatabricksUnityCompatibility.java index a3bd6e9579b0..84cb24502b8e 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveDatabricksUnityCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveDatabricksUnityCompatibility.java @@ -20,6 +20,7 @@ import org.testng.annotations.Test; import static io.trino.tempto.assertions.QueryAssert.Row.row; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DATABRICKS_UNITY_HTTP_HMS; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; @@ -28,7 +29,6 @@ import static io.trino.tests.product.utils.QueryExecutors.onDelta; import static io.trino.tests.product.utils.QueryExecutors.onTrino; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; public class TestHiveDatabricksUnityCompatibility @@ -41,8 +41,8 @@ public class TestHiveDatabricksUnityCompatibility @BeforeMethodWithContext public void setUp() { - unityCatalogName = requireNonNull(System.getenv("DATABRICKS_UNITY_CATALOG_NAME"), "Environment variable not set: DATABRICKS_UNITY_CATALOG_NAME"); - externalLocationPath = requireNonNull(System.getenv("DATABRICKS_UNITY_EXTERNAL_LOCATION"), "Environment variable not set: DATABRICKS_UNITY_EXTERNAL_LOCATION"); + unityCatalogName = requireEnv("DATABRICKS_UNITY_CATALOG_NAME"); + externalLocationPath = requireEnv("DATABRICKS_UNITY_EXTERNAL_LOCATION"); String schemaLocation = format("%s/%s", externalLocationPath, schemaName); onDelta().executeQuery("CREATE SCHEMA " + unityCatalogName + "." + schemaName + " MANAGED LOCATION '" + schemaLocation + "'"); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTableStatistics.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTableStatistics.java index bdf1c9a202ce..aff4c7072059 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTableStatistics.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTableStatistics.java @@ -1403,9 +1403,9 @@ public void testComputeStatisticsForTableWithOnlyDateColumns() @Test @Flaky(issue = RETRYABLE_FAILURES_ISSUES, match = RETRYABLE_FAILURES_MATCH) - public void testMixedHiveAndPrestoStatistics() + public void testMixedHiveAndTrinoStatistics() { - String tableName = "test_mixed_hive_and_presto_statistics"; + String tableName = "test_mixed_hive_and_trino_statistics"; onHive().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); onHive().executeQuery(format("CREATE TABLE %s (a INT) PARTITIONED BY (p INT) STORED AS ORC TBLPROPERTIES ('transactional' = 'false')", tableName)); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveViewsLegacy.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveViewsLegacy.java index 5cff6090ac5c..d49318f8f349 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveViewsLegacy.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveViewsLegacy.java @@ -114,7 +114,7 @@ public void testArrayIndexingInView() "[hive]\n" + "\n" + "actual rows:\n" + - "[presto]"); + "[trino]"); } @Override diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestRoles.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestRoles.java index 3b517e44c0c8..d214c71c6906 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestRoles.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestRoles.java @@ -128,20 +128,20 @@ public void testCreateDropRoleAccessControl() { // Only users that are granted with "admin" role can create, drop and list roles // Alice is not granted with "admin" role - assertQueryFailure(() -> onPrestoAlice().executeQuery(format("CREATE ROLE %s IN hive", ROLE3))) + assertQueryFailure(() -> onTrinoAlice().executeQuery(format("CREATE ROLE %s IN hive", ROLE3))) .hasMessageContaining("Cannot create role %s", ROLE3); - assertQueryFailure(() -> onPrestoAlice().executeQuery(format("DROP ROLE %s IN hive", ROLE3))) + assertQueryFailure(() -> onTrinoAlice().executeQuery(format("DROP ROLE %s IN hive", ROLE3))) .hasMessageContaining("Cannot drop role %s", ROLE3); - assertQueryFailure(() -> onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.roles")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.roles")) .hasMessageContaining("Cannot select from table information_schema.roles"); } @Test(groups = {AUTHORIZATION, PROFILE_SPECIFIC_TESTS}) public void testPublicRoleIsGrantedToEveryone() { - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .contains(row("alice", "USER", "public", "NO")); - assertThat(onPrestoBob().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoBob().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .contains(row("bob", "USER", "public", "NO")); } @@ -157,7 +157,7 @@ public void testGrantRoleToUser() { onTrino().executeQuery("CREATE ROLE role1 IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO")); @@ -170,7 +170,7 @@ public void testGrantRoleToRole() onTrino().executeQuery("CREATE ROLE role2 IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice IN hive"); onTrino().executeQuery("GRANT role2 TO ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO"), @@ -184,7 +184,7 @@ public void testGrantRoleWithAdminOption() onTrino().executeQuery("CREATE ROLE role2 IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice WITH ADMIN OPTION IN hive"); onTrino().executeQuery("GRANT role2 TO ROLE role1 WITH ADMIN OPTION IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "YES"), @@ -204,7 +204,7 @@ public void testGrantRoleMultipleTimes() onTrino().executeQuery("GRANT role2 TO ROLE role1 WITH ADMIN OPTION IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice WITH ADMIN OPTION IN hive"); onTrino().executeQuery("GRANT role2 TO ROLE role1 WITH ADMIN OPTION IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "YES"), @@ -216,13 +216,13 @@ public void testRevokeRoleFromUser() { onTrino().executeQuery("CREATE ROLE role1 IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO")); onTrino().executeQuery("REVOKE role1 FROM USER alice IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO")); } @@ -234,14 +234,14 @@ public void testRevokeRoleFromRole() onTrino().executeQuery("CREATE ROLE role2 IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice IN hive"); onTrino().executeQuery("GRANT role2 TO ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO"), row("role1", "ROLE", "role2", "NO")); onTrino().executeQuery("REVOKE role2 FROM ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO")); @@ -251,8 +251,8 @@ public void testRevokeRoleFromRole() public void testRevokeRoleFromOwner() { try { - onPrestoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); - assertThat(onPrestoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table")) + onTrinoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); + assertThat(onTrinoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table")) .containsOnly(ImmutableList.of( row("alice", "USER", "alice", "USER", "hive", "default", "test_table", "SELECT", "YES", null), row("alice", "USER", "alice", "USER", "hive", "default", "test_table", "DELETE", "YES", null), @@ -262,14 +262,14 @@ public void testRevokeRoleFromOwner() onTrino().executeQuery("REVOKE SELECT ON hive.default.test_table FROM USER alice"); // now there should be no SELECT privileges shown even though alice has OWNERSHIP - assertThat(onPrestoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table")) + assertThat(onTrinoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table")) .containsOnly(ImmutableList.of( row("alice", "USER", "alice", "USER", "hive", "default", "test_table", "DELETE", "YES", null), row("alice", "USER", "alice", "USER", "hive", "default", "test_table", "UPDATE", "YES", null), row("alice", "USER", "alice", "USER", "hive", "default", "test_table", "INSERT", "YES", null))); } finally { - onPrestoAlice().executeQuery("DROP TABLE IF EXISTS hive.default.test_table"); + onTrinoAlice().executeQuery("DROP TABLE IF EXISTS hive.default.test_table"); } } @@ -278,13 +278,13 @@ public void testDropGrantedRole() { onTrino().executeQuery("CREATE ROLE role1 IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO")); onTrino().executeQuery("DROP ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO")); } @@ -296,14 +296,14 @@ public void testRevokeTransitiveRoleFromUser() onTrino().executeQuery("CREATE ROLE role2 IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice IN hive"); onTrino().executeQuery("GRANT role2 TO ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO"), row("role1", "ROLE", "role2", "NO")); onTrino().executeQuery("REVOKE role1 FROM USER alice IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO")); } @@ -317,7 +317,7 @@ public void testRevokeTransitiveRoleFromRole() onTrino().executeQuery("GRANT role1 TO USER alice IN hive"); onTrino().executeQuery("GRANT role2 TO ROLE role1 IN hive"); onTrino().executeQuery("GRANT role3 TO ROLE role2 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO"), @@ -325,7 +325,7 @@ public void testRevokeTransitiveRoleFromRole() row("role2", "ROLE", "role3", "NO")); onTrino().executeQuery("REVOKE role2 FROM ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO")); @@ -340,7 +340,7 @@ public void testDropTransitiveRole() onTrino().executeQuery("GRANT role1 TO USER alice IN hive"); onTrino().executeQuery("GRANT role2 TO ROLE role1 IN hive"); onTrino().executeQuery("GRANT role3 TO ROLE role2 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO"), @@ -348,7 +348,7 @@ public void testDropTransitiveRole() row("role2", "ROLE", "role3", "NO")); onTrino().executeQuery("DROP ROLE role2 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO")); @@ -361,7 +361,7 @@ public void testRevokeAdminOption() onTrino().executeQuery("CREATE ROLE role2 IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice WITH ADMIN OPTION IN hive"); onTrino().executeQuery("GRANT role2 TO ROLE role1 WITH ADMIN OPTION IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "YES"), @@ -370,7 +370,7 @@ public void testRevokeAdminOption() onTrino().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER alice IN hive"); onTrino().executeQuery("REVOKE ADMIN OPTION FOR role2 FROM ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO"), @@ -384,7 +384,7 @@ public void testRevokeMultipleTimes() onTrino().executeQuery("CREATE ROLE role2 IN hive"); onTrino().executeQuery("GRANT role1 TO USER alice WITH ADMIN OPTION IN hive"); onTrino().executeQuery("GRANT role2 TO ROLE role1 WITH ADMIN OPTION IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "YES"), @@ -395,7 +395,7 @@ public void testRevokeMultipleTimes() onTrino().executeQuery("REVOKE ADMIN OPTION FOR role2 FROM ROLE role1 IN hive"); onTrino().executeQuery("REVOKE ADMIN OPTION FOR role2 FROM ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO"), row("alice", "USER", "role1", "NO"), @@ -406,7 +406,7 @@ public void testRevokeMultipleTimes() onTrino().executeQuery("REVOKE role2 FROM ROLE role1 IN hive"); onTrino().executeQuery("REVOKE role2 FROM ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.applicable_roles")) .containsOnly( row("alice", "USER", "public", "NO")); } @@ -417,50 +417,50 @@ public void testGrantRevokeRoleAccessControl() onTrino().executeQuery("CREATE ROLE role1 IN hive"); onTrino().executeQuery("CREATE ROLE role2 IN hive"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("GRANT role1 TO USER bob IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("GRANT role1 TO USER bob IN hive")) .hasMessageContaining("Cannot grant roles [role1] to [USER bob]"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive")) .hasMessageContaining("Cannot grant roles [role1] to [USER bob]"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive")) .hasMessageContaining("Cannot revoke roles [role1] from [USER bob]"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive")) .hasMessageContaining("Cannot revoke roles [role1] from [USER bob]"); onTrino().executeQuery("GRANT role1 TO USER alice WITH ADMIN OPTION IN hive"); - onPrestoAlice().executeQuery("GRANT role1 TO USER bob IN hive"); - onPrestoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive"); - onPrestoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive"); - onPrestoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive"); + onTrinoAlice().executeQuery("GRANT role1 TO USER bob IN hive"); + onTrinoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive"); + onTrinoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive"); + onTrinoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive"); onTrino().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER alice IN hive"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("GRANT role1 TO USER bob IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("GRANT role1 TO USER bob IN hive")) .hasMessageContaining("Cannot grant roles [role1] to [USER bob]"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive")) .hasMessageContaining("Cannot grant roles [role1] to [USER bob]"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive")) .hasMessageContaining("Cannot revoke roles [role1] from [USER bob]"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive")) .hasMessageContaining("Cannot revoke roles [role1] from [USER bob]"); onTrino().executeQuery("GRANT role2 TO USER alice IN hive"); onTrino().executeQuery("GRANT role1 TO ROLE role2 WITH ADMIN OPTION IN hive"); - onPrestoAlice().executeQuery("GRANT role1 TO USER bob IN hive"); - onPrestoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive"); - onPrestoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive"); - onPrestoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive"); + onTrinoAlice().executeQuery("GRANT role1 TO USER bob IN hive"); + onTrinoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive"); + onTrinoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive"); + onTrinoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive"); - onPrestoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM ROLE role2 IN hive"); + onTrinoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM ROLE role2 IN hive"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("GRANT role1 TO USER bob IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("GRANT role1 TO USER bob IN hive")) .hasMessageContaining("Cannot grant roles [role1] to [USER bob]"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("GRANT role1 TO USER bob WITH ADMIN OPTION IN hive")) .hasMessageContaining("Cannot grant roles [role1] to [USER bob]"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("REVOKE role1 FROM USER bob IN hive")) .hasMessageContaining("Cannot revoke roles [role1] from [USER bob]"); - assertQueryFailure(() -> onPrestoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("REVOKE ADMIN OPTION FOR role1 FROM USER bob IN hive")) .hasMessageContaining("Cannot revoke roles [role1] from [USER bob]"); } @@ -474,36 +474,36 @@ public void testSetRole() onTrino().executeQuery("GRANT role2 TO ROLE role1 IN hive"); onTrino().executeQuery("GRANT role3 TO ROLE role2 IN hive"); - onPrestoAlice().executeQuery("SET ROLE ALL IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) + onTrinoAlice().executeQuery("SET ROLE ALL IN hive"); + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) .containsOnly( row("public"), row("role1"), row("role2"), row("role3")); - onPrestoAlice().executeQuery("SET ROLE NONE IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) + onTrinoAlice().executeQuery("SET ROLE NONE IN hive"); + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) .containsOnly( row("public")); - onPrestoAlice().executeQuery("SET ROLE role1 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) + onTrinoAlice().executeQuery("SET ROLE role1 IN hive"); + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) .containsOnly( row("public"), row("role1"), row("role2"), row("role3")); - onPrestoAlice().executeQuery("SET ROLE role2 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) + onTrinoAlice().executeQuery("SET ROLE role2 IN hive"); + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) .containsOnly( row("public"), row("role2"), row("role3")); - onPrestoAlice().executeQuery("SET ROLE role3 IN hive"); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) + onTrinoAlice().executeQuery("SET ROLE role3 IN hive"); + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.enabled_roles")) .containsOnly( row("public"), row("role3")); @@ -536,11 +536,11 @@ public void testShowRoles() row("public"), row("admin"), row("role1")); - assertQueryFailure(() -> onPrestoAlice().executeQuery("SHOW ROLES FROM hive")) + assertQueryFailure(() -> onTrinoAlice().executeQuery("SHOW ROLES FROM hive")) .hasMessageContaining("Cannot show roles"); onTrino().executeQuery("GRANT admin TO alice IN hive"); - onPrestoAlice().executeQuery("SET ROLE admin IN hive"); - assertThat(onPrestoAlice().executeQuery("SHOW ROLES FROM hive")) + onTrinoAlice().executeQuery("SET ROLE admin IN hive"); + assertThat(onTrinoAlice().executeQuery("SHOW ROLES FROM hive")) .containsOnly( row("public"), row("admin"), @@ -550,20 +550,20 @@ public void testShowRoles() @Test(groups = {AUTHORIZATION, PROFILE_SPECIFIC_TESTS}) public void testShowCurrentRoles() { - assertThat(onPrestoAlice().executeQuery("SHOW CURRENT ROLES FROM hive")) + assertThat(onTrinoAlice().executeQuery("SHOW CURRENT ROLES FROM hive")) .containsOnly( row("public")); onTrino().executeQuery("CREATE ROLE role1 IN hive"); onTrino().executeQuery("CREATE ROLE role2 IN hive"); onTrino().executeQuery("GRANT role1 TO alice IN hive"); onTrino().executeQuery("GRANT role2 TO alice IN hive"); - assertThat(onPrestoAlice().executeQuery("SHOW CURRENT ROLES FROM hive")) + assertThat(onTrinoAlice().executeQuery("SHOW CURRENT ROLES FROM hive")) .containsOnly( row("public"), row("role1"), row("role2")); - onPrestoAlice().executeQuery("SET ROLE role2 IN hive"); - assertThat(onPrestoAlice().executeQuery("SHOW CURRENT ROLES FROM hive")) + onTrinoAlice().executeQuery("SET ROLE role2 IN hive"); + assertThat(onTrinoAlice().executeQuery("SHOW CURRENT ROLES FROM hive")) .containsOnly( row("public"), row("role2")); @@ -576,7 +576,7 @@ public void testShowRoleGrants() .containsOnly( row("public"), row("admin")); - assertThat(onPrestoAlice().executeQuery("SHOW ROLE GRANTS FROM hive")) + assertThat(onTrinoAlice().executeQuery("SHOW ROLE GRANTS FROM hive")) .containsOnly( row("public")); onTrino().executeQuery("CREATE ROLE role1 IN hive"); @@ -587,7 +587,7 @@ public void testShowRoleGrants() .containsOnly( row("public"), row("admin")); - assertThat(onPrestoAlice().executeQuery("SHOW ROLE GRANTS FROM hive")) + assertThat(onTrinoAlice().executeQuery("SHOW ROLE GRANTS FROM hive")) .containsOnly( row("public"), row("role1")); @@ -603,61 +603,61 @@ public void testSetRoleCreateDropSchema() @Test(groups = {AUTHORIZATION, PROFILE_SPECIFIC_TESTS}) public void testAdminCanDropAnyTable() { - onPrestoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); + onTrinoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); assertAdminExecute("DROP TABLE hive.default.test_table"); } @Test(groups = {AUTHORIZATION, PROFILE_SPECIFIC_TESTS}) public void testAdminCanRenameAnyTable() { - onPrestoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); + onTrinoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); assertAdminExecute("ALTER TABLE hive.default.test_table RENAME TO hive.default.test_table_1"); - onPrestoAlice().executeQuery("DROP TABLE hive.default.test_table_1"); + onTrinoAlice().executeQuery("DROP TABLE hive.default.test_table_1"); } @Test(groups = {AUTHORIZATION, PROFILE_SPECIFIC_TESTS}) public void testAdminCanAddColumnToAnyTable() { - onPrestoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); + onTrinoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); assertAdminExecute("ALTER TABLE hive.default.test_table ADD COLUMN bar DATE"); - onPrestoAlice().executeQuery("DROP TABLE hive.default.test_table"); + onTrinoAlice().executeQuery("DROP TABLE hive.default.test_table"); } @Test(groups = {AUTHORIZATION, PROFILE_SPECIFIC_TESTS}) public void testAdminCanRenameColumnInAnyTable() { - onPrestoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); + onTrinoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); assertAdminExecute("ALTER TABLE hive.default.test_table RENAME COLUMN foo TO bar"); - onPrestoAlice().executeQuery("DROP TABLE hive.default.test_table"); + onTrinoAlice().executeQuery("DROP TABLE hive.default.test_table"); } @Test(groups = {AUTHORIZATION, PROFILE_SPECIFIC_TESTS}) public void testAdminCanShowAllGrants() { try { - onPrestoBob().executeQuery("CREATE TABLE hive.default.test_table_bob (foo BIGINT)"); - onPrestoAlice().executeQuery("CREATE TABLE hive.default.test_table_alice (foo BIGINT)"); + onTrinoBob().executeQuery("CREATE TABLE hive.default.test_table_bob (foo BIGINT)"); + onTrinoAlice().executeQuery("CREATE TABLE hive.default.test_table_alice (foo BIGINT)"); onTrino().executeQuery("GRANT admin TO alice IN hive"); - onPrestoAlice().executeQuery("SET ROLE ADMIN IN hive"); + onTrinoAlice().executeQuery("SET ROLE ADMIN IN hive"); - assertThat(onPrestoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table_alice")) + assertThat(onTrinoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table_alice")) .containsOnly(ImmutableList.of( row("alice", "USER", "alice", "USER", "hive", "default", "test_table_alice", "SELECT", "YES", null), row("alice", "USER", "alice", "USER", "hive", "default", "test_table_alice", "DELETE", "YES", null), row("alice", "USER", "alice", "USER", "hive", "default", "test_table_alice", "UPDATE", "YES", null), row("alice", "USER", "alice", "USER", "hive", "default", "test_table_alice", "INSERT", "YES", null))); - assertThat(onPrestoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table_bob")) + assertThat(onTrinoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table_bob")) .containsOnly(ImmutableList.of( row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "SELECT", "YES", null), row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "DELETE", "YES", null), row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "UPDATE", "YES", null), row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "INSERT", "YES", null))); - onPrestoAlice().executeQuery("GRANT SELECT ON hive.default.test_table_alice TO bob WITH GRANT OPTION"); - onPrestoAlice().executeQuery("GRANT INSERT ON hive.default.test_table_alice TO bob"); + onTrinoAlice().executeQuery("GRANT SELECT ON hive.default.test_table_alice TO bob WITH GRANT OPTION"); + onTrinoAlice().executeQuery("GRANT INSERT ON hive.default.test_table_alice TO bob"); - assertThat(onPrestoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table_alice")) + assertThat(onTrinoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table_alice")) .containsOnly(ImmutableList.of( row("alice", "USER", "alice", "USER", "hive", "default", "test_table_alice", "SELECT", "YES", null), row("alice", "USER", "alice", "USER", "hive", "default", "test_table_alice", "DELETE", "YES", null), @@ -667,8 +667,8 @@ public void testAdminCanShowAllGrants() row("alice", "USER", "bob", "USER", "hive", "default", "test_table_alice", "INSERT", "NO", null))); } finally { - onPrestoAlice().executeQuery("DROP TABLE IF EXISTS hive.default.test_table_alice"); - onPrestoBob().executeQuery("DROP TABLE IF EXISTS hive.default.test_table_bob"); + onTrinoAlice().executeQuery("DROP TABLE IF EXISTS hive.default.test_table_alice"); + onTrinoBob().executeQuery("DROP TABLE IF EXISTS hive.default.test_table_bob"); onTrino().executeQuery("REVOKE admin FROM alice IN hive"); } } @@ -677,26 +677,26 @@ public void testAdminCanShowAllGrants() public void testAdminCanShowGrantsOnlyFromCurrentSchema() { try { - onPrestoBob().executeQuery("CREATE TABLE hive.default.test_table_bob (foo BIGINT)"); + onTrinoBob().executeQuery("CREATE TABLE hive.default.test_table_bob (foo BIGINT)"); onTrino().executeQuery("CREATE SCHEMA hive.test"); onTrino().executeQuery("GRANT admin TO alice IN hive"); - onPrestoAlice().executeQuery("SET ROLE ADMIN IN hive"); - onPrestoAlice().executeQuery("CREATE TABLE hive.test.test_table_bob (foo BIGINT) with (external_location='/tmp')"); + onTrinoAlice().executeQuery("SET ROLE ADMIN IN hive"); + onTrinoAlice().executeQuery("CREATE TABLE hive.test.test_table_bob (foo BIGINT) with (external_location='/tmp')"); - assertThat(onPrestoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table_bob")) + assertThat(onTrinoAlice().executeQuery("SHOW GRANTS ON hive.default.test_table_bob")) .containsOnly(ImmutableList.of( row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "SELECT", "YES", null), row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "DELETE", "YES", null), row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "UPDATE", "YES", null), row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "INSERT", "YES", null))); - assertThat(onPrestoAlice().executeQuery("SHOW GRANTS ON hive.test.test_table_bob")) + assertThat(onTrinoAlice().executeQuery("SHOW GRANTS ON hive.test.test_table_bob")) .containsOnly(ImmutableList.of( row("alice", "USER", "alice", "USER", "hive", "test", "test_table_bob", "SELECT", "YES", null), row("alice", "USER", "alice", "USER", "hive", "test", "test_table_bob", "DELETE", "YES", null), row("alice", "USER", "alice", "USER", "hive", "test", "test_table_bob", "UPDATE", "YES", null), row("alice", "USER", "alice", "USER", "hive", "test", "test_table_bob", "INSERT", "YES", null))); - assertThat(onPrestoAlice().executeQuery("SELECT * FROM hive.information_schema.table_privileges where table_name = 'test_table_bob'")) + assertThat(onTrinoAlice().executeQuery("SELECT * FROM hive.information_schema.table_privileges where table_name = 'test_table_bob'")) .containsOnly(ImmutableList.of( row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "SELECT", "YES", null), row("bob", "USER", "bob", "USER", "hive", "default", "test_table_bob", "DELETE", "YES", null), @@ -708,8 +708,8 @@ public void testAdminCanShowGrantsOnlyFromCurrentSchema() row("alice", "USER", "alice", "USER", "hive", "test", "test_table_bob", "INSERT", "YES", null))); } finally { - onPrestoBob().executeQuery("DROP TABLE IF EXISTS hive.default.test_table_bob"); - onPrestoAlice().executeQuery("DROP TABLE IF EXISTS hive.test.test_table_bob"); + onTrinoBob().executeQuery("DROP TABLE IF EXISTS hive.default.test_table_bob"); + onTrinoAlice().executeQuery("DROP TABLE IF EXISTS hive.test.test_table_bob"); onTrino().executeQuery("DROP SCHEMA IF EXISTS hive.test"); onTrino().executeQuery("REVOKE admin FROM alice IN hive"); } @@ -724,9 +724,9 @@ public void testSetRoleTablePermissions() onTrino().executeQuery("GRANT role1 TO USER bob IN hive"); onTrino().executeQuery("GRANT role2 TO USER bob IN hive"); - onPrestoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); - onPrestoAlice().executeQuery("GRANT SELECT ON hive.default.test_table TO ROLE role1"); - onPrestoAlice().executeQuery("GRANT INSERT ON hive.default.test_table TO ROLE role2"); + onTrinoAlice().executeQuery("CREATE TABLE hive.default.test_table (foo BIGINT)"); + onTrinoAlice().executeQuery("GRANT SELECT ON hive.default.test_table TO ROLE role1"); + onTrinoAlice().executeQuery("GRANT INSERT ON hive.default.test_table TO ROLE role2"); String select = "SELECT * FROM hive.default.test_table"; String insert = "INSERT INTO hive.default.test_table (foo) VALUES (1)"; @@ -734,46 +734,46 @@ public void testSetRoleTablePermissions() assertAdminExecute(select); assertAdminExecute(insert); - onPrestoBob().executeQuery(select); - onPrestoBob().executeQuery(insert); - assertThat(onPrestoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) + onTrinoBob().executeQuery(select); + onTrinoBob().executeQuery(insert); + assertThat(onTrinoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) .containsOnly(ImmutableList.of( row("alice", "USER", "role1", "ROLE", "hive", "default", "test_table", "SELECT", "NO", null), row("alice", "USER", "role2", "ROLE", "hive", "default", "test_table", "INSERT", "NO", null))); - onPrestoBob().executeQuery("SET ROLE ALL IN hive"); - onPrestoBob().executeQuery(select); - onPrestoBob().executeQuery(insert); - assertThat(onPrestoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) + onTrinoBob().executeQuery("SET ROLE ALL IN hive"); + onTrinoBob().executeQuery(select); + onTrinoBob().executeQuery(insert); + assertThat(onTrinoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) .containsOnly(ImmutableList.of( row("alice", "USER", "role1", "ROLE", "hive", "default", "test_table", "SELECT", "NO", null), row("alice", "USER", "role2", "ROLE", "hive", "default", "test_table", "INSERT", "NO", null))); - onPrestoBob().executeQuery("SET ROLE NONE IN hive"); - assertQueryFailure(() -> onPrestoBob().executeQuery(select)) + onTrinoBob().executeQuery("SET ROLE NONE IN hive"); + assertQueryFailure(() -> onTrinoBob().executeQuery(select)) .hasMessageContaining("Access Denied"); - assertQueryFailure(() -> onPrestoBob().executeQuery(insert)) + assertQueryFailure(() -> onTrinoBob().executeQuery(insert)) .hasMessageContaining("Access Denied"); - assertThat(onPrestoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) + assertThat(onTrinoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) .containsOnly(ImmutableList.of()); - onPrestoBob().executeQuery("SET ROLE role1 IN hive"); - onPrestoBob().executeQuery(select); - assertQueryFailure(() -> onPrestoBob().executeQuery(insert)) + onTrinoBob().executeQuery("SET ROLE role1 IN hive"); + onTrinoBob().executeQuery(select); + assertQueryFailure(() -> onTrinoBob().executeQuery(insert)) .hasMessageContaining("Access Denied"); - assertThat(onPrestoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) + assertThat(onTrinoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) .containsOnly(ImmutableList.of( row("alice", "USER", "role1", "ROLE", "hive", "default", "test_table", "SELECT", "NO", null))); - onPrestoBob().executeQuery("SET ROLE role2 IN hive"); - assertQueryFailure(() -> onPrestoBob().executeQuery(select)) + onTrinoBob().executeQuery("SET ROLE role2 IN hive"); + assertQueryFailure(() -> onTrinoBob().executeQuery(select)) .hasMessageContaining("Access Denied"); - onPrestoBob().executeQuery(insert); - assertThat(onPrestoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) + onTrinoBob().executeQuery(insert); + assertThat(onTrinoBob().executeQuery("SHOW GRANTS ON hive.default.test_table")) .containsOnly(ImmutableList.of( row("alice", "USER", "role2", "ROLE", "hive", "default", "test_table", "INSERT", "NO", null))); - onPrestoAlice().executeQuery("DROP TABLE hive.default.test_table"); + onTrinoAlice().executeQuery("DROP TABLE hive.default.test_table"); } private static void assertAdminExecute(String query) @@ -790,12 +790,12 @@ private static void assertAdminExecute(String query) onTrino().executeQuery(query); } - private static QueryExecutor onPrestoAlice() + private static QueryExecutor onTrinoAlice() { return connectToTrino("alice@trino"); } - private static QueryExecutor onPrestoBob() + private static QueryExecutor onTrinoBob() { return connectToTrino("bob@trino"); } 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 91c0702c0068..c42783245eaf 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 @@ -23,6 +23,7 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.tempto.assertions.QueryAssert.assertQueryFailure; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.HIVE_HUDI_REDIRECTIONS; import static io.trino.tests.product.TestGroups.HUDI; @@ -30,7 +31,6 @@ import static io.trino.tests.product.utils.QueryExecutors.onHudi; import static io.trino.tests.product.utils.QueryExecutors.onTrino; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; public class TestHudiSparkCompatibility @@ -44,7 +44,7 @@ public class TestHudiSparkCompatibility @BeforeMethodWithContext public void setUp() { - bucketName = requireNonNull(System.getenv("S3_BUCKET"), "Environment variable not set: S3_BUCKET"); + bucketName = requireEnv("S3_BUCKET"); } @Test(groups = {HUDI, PROFILE_SPECIFIC_TESTS}) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergAlluxioCaching.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergAlluxioCaching.java index 4fa903cc1731..8ee6e3a9e424 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergAlluxioCaching.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergAlluxioCaching.java @@ -19,12 +19,12 @@ import io.trino.tests.product.utils.CachingTestUtils.CacheStats; import org.testng.annotations.Test; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.TestGroups.ICEBERG_ALLUXIO_CACHING; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; import static io.trino.tests.product.utils.CachingTestUtils.getCacheStats; import static io.trino.tests.product.utils.QueryAssertions.assertEventually; import static io.trino.tests.product.utils.QueryExecutors.onTrino; -import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; @@ -36,7 +36,7 @@ public class TestIcebergAlluxioCaching @BeforeMethodWithContext public void setUp() { - bucketName = requireNonNull(System.getenv("S3_BUCKET"), "Environment variable not set: S3_BUCKET"); + bucketName = requireEnv("S3_BUCKET"); } @Test(groups = {ICEBERG_ALLUXIO_CACHING, PROFILE_SPECIFIC_TESTS}) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergAzure.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergAzure.java index e689e1dcb84a..6fe56c3dfb6f 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergAzure.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergAzure.java @@ -19,10 +19,10 @@ import io.trino.tests.product.BaseTestTableFormats; import org.testng.annotations.Test; +import static io.trino.testing.SystemEnvironmentUtils.requireEnv; import static io.trino.tests.product.TestGroups.ICEBERG_AZURE; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; import static java.lang.String.format; -import static java.util.Objects.requireNonNull; public class TestIcebergAzure extends BaseTestTableFormats @@ -35,8 +35,8 @@ public class TestIcebergAzure @BeforeMethodWithContext public void setUp() { - String container = requireNonNull(System.getenv("ABFS_CONTAINER"), "Environment variable not set: ABFS_CONTAINER"); - String account = requireNonNull(System.getenv("ABFS_ACCOUNT"), "Environment variable not set: ABFS_ACCOUNT"); + String container = requireEnv("ABFS_CONTAINER"); + String account = requireEnv("ABFS_ACCOUNT"); schemaLocation = format("abfs://%s@%s.dfs.core.windows.net/%s", container, account, schema); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergProcedureCalls.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergProcedureCalls.java index 61deb2f3d90e..ca90567730d4 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergProcedureCalls.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergProcedureCalls.java @@ -366,18 +366,6 @@ public void testRollbackToSnapshot() onTrino().executeQuery(format("DROP TABLE IF EXISTS %s", tableName)); } - @Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS}) - public void testRollbackToSnapshotWithNullArgument() - { - onTrino().executeQuery("USE iceberg.default"); - assertQueryFailure(() -> onTrino().executeQuery("CALL system.rollback_to_snapshot(NULL, 'customer_orders', 8954597067493422955)")) - .hasMessageMatching(".*schema cannot be null.*"); - assertQueryFailure(() -> onTrino().executeQuery("CALL system.rollback_to_snapshot('testdb', NULL, 8954597067493422955)")) - .hasMessageMatching(".*table cannot be null.*"); - assertQueryFailure(() -> onTrino().executeQuery("CALL system.rollback_to_snapshot('testdb', 'customer_orders', NULL)")) - .hasMessageMatching(".*snapshot_id cannot be null.*"); - } - private long getSecondOldestTableSnapshot(String tableName) { return (Long) onTrino().executeQuery( diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/jdbc/BaseLdapJdbcTest.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/jdbc/BaseLdapJdbcTest.java index 869c08584868..2932f1ec0d73 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/jdbc/BaseLdapJdbcTest.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/jdbc/BaseLdapJdbcTest.java @@ -58,7 +58,7 @@ public abstract class BaseLdapJdbcTest @Inject @Named("databases.trino.cli_ldap_server_address") - private String prestoServer; + private String trinoServer; @Override public Requirement getRequirements(Configuration configuration) @@ -89,16 +89,16 @@ private Connection getLdapConnection(String name, String password) return DriverManager.getConnection(getLdapUrl(), name, password); } - protected String prestoServer() + protected String trinoServer() { String prefix = "https://"; - checkState(prestoServer.startsWith(prefix), "invalid server address: %s", prestoServer); - return prestoServer.substring(prefix.length()); + checkState(trinoServer.startsWith(prefix), "invalid server address: %s", trinoServer); + return trinoServer.substring(prefix.length()); } protected String getLdapUrl() { - return format(getLdapUrlFormat(), prestoServer(), ldapTruststorePath, ldapTruststorePassword); + return format(getLdapUrlFormat(), trinoServer(), ldapTruststorePath, ldapTruststorePassword); } protected abstract String getLdapUrlFormat(); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/jdbc/TestLdapTrinoJdbc.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/jdbc/TestLdapTrinoJdbc.java index 551be50ab413..f98d2fb8fe34 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/jdbc/TestLdapTrinoJdbc.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/jdbc/TestLdapTrinoJdbc.java @@ -128,7 +128,7 @@ public void shouldFailQueryForLdapWithoutPassword() @Test(groups = {LDAP, TRINO_JDBC, PROFILE_SPECIFIC_TESTS}, timeOut = TIMEOUT) public void shouldFailQueryForLdapWithoutSsl() { - assertThatThrownBy(() -> DriverManager.getConnection("jdbc:trino://" + prestoServer(), ldapUserName, ldapUserPassword)) + assertThatThrownBy(() -> DriverManager.getConnection("jdbc:trino://" + trinoServer(), ldapUserName, ldapUserPassword)) .isInstanceOf(SQLException.class) .hasMessageContaining("TLS/SSL is required for authentication with username and password"); } @@ -136,7 +136,7 @@ public void shouldFailQueryForLdapWithoutSsl() @Test(groups = {LDAP, TRINO_JDBC, PROFILE_SPECIFIC_TESTS}, timeOut = TIMEOUT) public void shouldFailForIncorrectTrustStore() { - String url = format("jdbc:trino://%s?SSL=true&SSLTrustStorePath=%s&SSLTrustStorePassword=%s", prestoServer(), ldapTruststorePath, "wrong_password"); + String url = format("jdbc:trino://%s?SSL=true&SSLTrustStorePath=%s&SSLTrustStorePassword=%s", trinoServer(), ldapTruststorePath, "wrong_password"); assertThatThrownBy(() -> DriverManager.getConnection(url, ldapUserName, ldapUserPassword)) .isInstanceOf(SQLException.class) .hasMessageContaining("Error setting up SSL: keystore password was incorrect"); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/utils/CachingTestUtils.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/utils/CachingTestUtils.java index a1ce1d3a8c95..3e28296ac6e9 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/utils/CachingTestUtils.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/utils/CachingTestUtils.java @@ -27,7 +27,7 @@ public static CacheStats getCacheStats(String catalog) QueryResult queryResult = onTrino().executeQuery("SELECT " + " sum(\"cachereads.alltime.count\") as cachereads, " + " sum(\"externalreads.alltime.count\") as externalreads " + - "FROM jmx.current.\"io.trino.filesystem.alluxio:name=" + catalog + ",type=alluxiocachestats\";"); + "FROM jmx.current.\"io.trino.filesystem.alluxio:catalog=" + catalog + ",name=" + catalog + ",type=alluxiocachestats\";"); double cacheReads = (Double) getOnlyElement(queryResult.rows()) .get(queryResult.tryFindColumnIndex("cachereads").get() - 1); diff --git a/testing/trino-server-dev/pom.xml b/testing/trino-server-dev/pom.xml index c7d57af2e149..3ca183c4f022 100644 --- a/testing/trino-server-dev/pom.xml +++ b/testing/trino-server-dev/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-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 90af66fba5b2..ca0371e655aa 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 - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml @@ -14,7 +14,7 @@ - 468-SNAPSHOT + 469-SNAPSHOT diff --git a/testing/trino-test-jdbc-compatibility-old-server/pom.xml b/testing/trino-test-jdbc-compatibility-old-server/pom.xml index ac6f6ccd1e2d..ba993b65a10d 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 - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-containers/pom.xml b/testing/trino-testing-containers/pom.xml index ab8dad0dae4d..90c73d3d3d89 100644 --- a/testing/trino-testing-containers/pom.xml +++ b/testing/trino-testing-containers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/TestContainers.java b/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/TestContainers.java index 03330b35f54c..9f9e0886fd34 100644 --- a/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/TestContainers.java +++ b/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/TestContainers.java @@ -31,6 +31,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.padEnd; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.testing.SystemEnvironmentUtils.isEnvSet; import static io.trino.testing.containers.ConditionalPullPolicy.TESTCONTAINERS_NEVER_PULL; import static java.lang.Boolean.parseBoolean; import static java.lang.System.getenv; @@ -72,7 +73,7 @@ public static String getPathFromClassPathResource(String resourcePath) public static void exposeFixedPorts(GenericContainer container) { - checkState(System.getenv("CONTINUOUS_INTEGRATION") == null, "" + + checkState(isEnvSet("CONTINUOUS_INTEGRATION"), "" + "Exposing fixed ports should not be used in regular test code. This could break parallel test execution. " + "This method is supposed to be invoked from local development helpers only e.g. QueryRunner.main(), " + "hence it should never run on CI"); diff --git a/testing/trino-testing-kafka/pom.xml b/testing/trino-testing-kafka/pom.xml index 25fe5d7fa499..55a6483baa7a 100644 --- a/testing/trino-testing-kafka/pom.xml +++ b/testing/trino-testing-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-resources/pom.xml b/testing/trino-testing-resources/pom.xml index 924468815c7c..28fadb11c6f8 100644 --- a/testing/trino-testing-resources/pom.xml +++ b/testing/trino-testing-resources/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-services/pom.xml b/testing/trino-testing-services/pom.xml index a58acd49b3f1..37b9d5d8f600 100644 --- a/testing/trino-testing-services/pom.xml +++ b/testing/trino-testing-services/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DatabaseFunctionSignatureKey.java b/testing/trino-testing-services/src/main/java/io/trino/testing/SystemEnvironmentUtils.java similarity index 56% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DatabaseFunctionSignatureKey.java rename to testing/trino-testing-services/src/main/java/io/trino/testing/SystemEnvironmentUtils.java index 905d23fcdd50..0f866033cf56 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DatabaseFunctionSignatureKey.java +++ b/testing/trino-testing-services/src/main/java/io/trino/testing/SystemEnvironmentUtils.java @@ -11,19 +11,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.metastore; +package io.trino.testing; import static java.util.Objects.requireNonNull; -public record DatabaseFunctionSignatureKey( - String databaseName, - String functionName, - String signatureToken) +public final class SystemEnvironmentUtils { - public DatabaseFunctionSignatureKey + private SystemEnvironmentUtils() {} + + /** + * Get the named environment variable, throwing an exception if it is not set. + */ + public static String requireEnv(String variable) + { + return requireNonNull(System.getenv(variable), () -> "environment variable not set: " + variable); + } + + public static boolean isEnvSet(String variable) { - requireNonNull(databaseName, "databaseName is null"); - requireNonNull(functionName, "functionName is null"); - requireNonNull(signatureToken, "signatureToken is null"); + return System.getenv(variable) != null; } } diff --git a/testing/trino-testing-services/src/main/java/io/trino/testing/services/junit/LogTestDurationListener.java b/testing/trino-testing-services/src/main/java/io/trino/testing/services/junit/LogTestDurationListener.java index 33127d251bb2..8263d6196b3c 100644 --- a/testing/trino-testing-services/src/main/java/io/trino/testing/services/junit/LogTestDurationListener.java +++ b/testing/trino-testing-services/src/main/java/io/trino/testing/services/junit/LogTestDurationListener.java @@ -37,6 +37,7 @@ import static com.google.common.base.Throwables.getStackTraceAsString; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static io.airlift.units.Duration.nanosSince; +import static io.trino.testing.SystemEnvironmentUtils.isEnvSet; import static io.trino.testing.services.junit.Listeners.reportListenerFailure; import static java.lang.String.format; import static java.lang.management.ManagementFactory.getThreadMXBean; @@ -73,7 +74,7 @@ private static boolean isEnabled() if (System.getProperty("LogTestDurationListener.enabled") != null) { return Boolean.getBoolean("LogTestDurationListener.enabled"); } - if (System.getenv("CONTINUOUS_INTEGRATION") != null) { + if (isEnvSet("CONTINUOUS_INTEGRATION")) { return true; } // For local development, logging durations is not typically useful. diff --git a/testing/trino-testing-services/src/main/java/io/trino/testng/services/FlakyTestRetryAnalyzer.java b/testing/trino-testing-services/src/main/java/io/trino/testng/services/FlakyTestRetryAnalyzer.java index 849403bea407..8ff39a4f6a98 100644 --- a/testing/trino-testing-services/src/main/java/io/trino/testng/services/FlakyTestRetryAnalyzer.java +++ b/testing/trino-testing-services/src/main/java/io/trino/testng/services/FlakyTestRetryAnalyzer.java @@ -28,6 +28,7 @@ import java.util.regex.Pattern; import static com.google.common.base.Throwables.getStackTraceAsString; +import static io.trino.testing.SystemEnvironmentUtils.isEnvSet; import static java.lang.String.format; public class FlakyTestRetryAnalyzer @@ -54,7 +55,7 @@ public boolean retry(ITestResult result) Optional enabledSystemPropertyValue = Optional.ofNullable(System.getProperty(ENABLED_SYSTEM_PROPERTY)); if (!enabledSystemPropertyValue.map(Boolean::parseBoolean) - .orElseGet(() -> System.getenv("CONTINUOUS_INTEGRATION") != null)) { + .orElseGet(() -> isEnvSet("CONTINUOUS_INTEGRATION"))) { log.info( "FlakyTestRetryAnalyzer not enabled: " + "CONTINUOUS_INTEGRATION environment is not detected or " + diff --git a/testing/trino-testing-services/src/main/java/io/trino/testng/services/LogTestDurationListener.java b/testing/trino-testing-services/src/main/java/io/trino/testng/services/LogTestDurationListener.java index f5e0e4d726b7..25a933487715 100644 --- a/testing/trino-testing-services/src/main/java/io/trino/testng/services/LogTestDurationListener.java +++ b/testing/trino-testing-services/src/main/java/io/trino/testng/services/LogTestDurationListener.java @@ -39,6 +39,7 @@ import static com.google.common.base.Throwables.getStackTraceAsString; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static io.airlift.units.Duration.nanosSince; +import static io.trino.testing.SystemEnvironmentUtils.isEnvSet; import static io.trino.testng.services.Listeners.formatTestName; import static io.trino.testng.services.Listeners.reportListenerFailure; import static java.lang.String.format; @@ -79,7 +80,7 @@ private static boolean isEnabled() if (System.getProperty("LogTestDurationListener.enabled") != null) { return Boolean.getBoolean("LogTestDurationListener.enabled"); } - if (System.getenv("CONTINUOUS_INTEGRATION") != null) { + if (isEnvSet("CONTINUOUS_INTEGRATION")) { return true; } // LogTestDurationListener does not support concurrent invocations of same test method 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 3f61b2496044..8a0011e699ee 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 @@ -25,6 +25,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import static io.trino.testing.SystemEnvironmentUtils.isEnvSet; import static io.trino.testng.services.Listeners.formatTestName; import static java.lang.String.format; @@ -47,7 +48,7 @@ private static boolean isEnabled() if (System.getProperty("ProgressLoggingListener.enabled") != null) { return Boolean.getBoolean("ProgressLoggingListener.enabled"); } - if (System.getenv("CONTINUOUS_INTEGRATION") != null) { + if (isEnvSet("CONTINUOUS_INTEGRATION")) { return true; } // most often not useful for local development diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index 0a2f9d1c1786..438992b4764d 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 2517e923d4d1..ad314ce27967 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 @@ -13,6 +13,7 @@ */ package io.trino.testing; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.MoreCollectors; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -58,6 +59,7 @@ import io.trino.sql.tree.ExplainType; import io.trino.testing.QueryRunner.MaterializedResultWithPlan; import io.trino.testing.TestingAccessControlManager.TestingPrivilege; +import io.trino.testing.sql.TestTable; import org.assertj.core.api.AssertProvider; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.AfterAll; @@ -667,6 +669,16 @@ private Optional tryGetDistributedQueryRunner() return Optional.empty(); } + protected TestTable newTrinoTable(String namePrefix, @Language("SQL") String tableDefinition) + { + return newTrinoTable(namePrefix, tableDefinition, ImmutableList.of()); + } + + protected TestTable newTrinoTable(String namePrefix, @Language("SQL") String tableDefinition, List rowsToInsert) + { + return new TestTable(getQueryRunner()::execute, namePrefix, tableDefinition, rowsToInsert); + } + protected Session noJoinReordering() { return noJoinReordering(JoinDistributionType.PARTITIONED); diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorSmokeTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorSmokeTest.java index b491e6ab375c..dfc875d2694a 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorSmokeTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorSmokeTest.java @@ -189,7 +189,7 @@ public void testInsert() throw new AssertionError("Cannot test INSERT without CREATE TABLE, the test needs to be implemented in a connector-specific way"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_", getCreateTableDefaultDefinition())) { + try (TestTable table = newTrinoTable("test_insert_", getCreateTableDefaultDefinition())) { assertUpdate("INSERT INTO " + table.getName() + " (a, b) VALUES (42, -38.5), (13, 99.9)", 2); assertThat(query("SELECT CAST(a AS bigint), b FROM " + table.getName())) .matches(expectedValues("(42, -38.5), (13, 99.9)")); @@ -205,7 +205,7 @@ public void verifySupportsDeleteDeclaration() } assumeTrue(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_supports_delete", "AS SELECT * FROM region")) { + try (TestTable table = newTrinoTable("test_supports_delete", "AS SELECT * FROM region")) { assertQueryFails("DELETE FROM " + table.getName(), MODIFYING_ROWS_MESSAGE); } } @@ -219,7 +219,7 @@ public void verifySupportsRowLevelDeleteDeclaration() } assumeTrue(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_supports_row_level_delete", "AS SELECT * FROM region")) { + try (TestTable table = newTrinoTable("test_supports_row_level_delete", "AS SELECT * FROM region")) { assertQueryFails("DELETE FROM " + table.getName() + " WHERE regionkey = 2", MODIFYING_ROWS_MESSAGE); } } @@ -233,7 +233,7 @@ public void verifySupportsUpdateDeclaration() } assumeTrue(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_supports_update", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_supports_update", "AS SELECT * FROM nation")) { assertQueryFails("UPDATE " + table.getName() + " SET nationkey = 100 WHERE regionkey = 2", MODIFYING_ROWS_MESSAGE); } } @@ -247,7 +247,7 @@ public void verifySupportsRowLevelUpdateDeclaration() } assumeTrue(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_row_update", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_row_update", "AS SELECT * FROM nation")) { assertQueryFails("UPDATE " + table.getName() + " SET nationkey = nationkey * 100 WHERE regionkey = 2", MODIFYING_ROWS_MESSAGE); } } @@ -256,7 +256,7 @@ public void verifySupportsRowLevelUpdateDeclaration() public void testUpdate() { assumeTrue(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_UPDATE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_row_update", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_row_update", "AS SELECT * FROM nation")) { assertUpdate("UPDATE " + table.getName() + " SET nationkey = 100 WHERE regionkey = 2", 5); assertQuery("SELECT count(*) FROM " + table.getName() + " WHERE nationkey = 100", "VALUES 5"); } @@ -266,7 +266,7 @@ public void testUpdate() public void testDeleteAllDataFromTable() { assumeTrue(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_DELETE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_all_data", "AS SELECT * FROM region")) { + try (TestTable table = newTrinoTable("test_delete_all_data", "AS SELECT * FROM region")) { // not using assertUpdate as some connectors provide update count and some do not getQueryRunner().execute("DELETE FROM " + table.getName()); assertQuery("SELECT count(*) FROM " + table.getName(), "VALUES 0"); @@ -278,7 +278,7 @@ public void testRowLevelDelete() { assumeTrue(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_ROW_LEVEL_DELETE)); // TODO (https://github.com/trinodb/trino/issues/5901) Use longer table name once Oracle version is updated - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_row_delete", "AS SELECT * FROM region")) { + try (TestTable table = newTrinoTable("test_row_delete", "AS SELECT * FROM region")) { assertUpdate("DELETE FROM " + table.getName() + " WHERE regionkey = 2", 1); assertThat(query("SELECT * FROM " + table.getName() + " WHERE regionkey = 2")) .returnsEmptyResult(); @@ -298,7 +298,7 @@ public void testTruncateTable() assumeTrue(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_truncate", "AS SELECT * FROM region")) { + try (TestTable table = newTrinoTable("test_truncate", "AS SELECT * FROM region")) { assertUpdate("TRUNCATE TABLE " + table.getName()); assertThat(query("TABLE " + table.getName())) .returnsEmptyResult(); @@ -318,7 +318,7 @@ public void testRowLevelUpdate() throw new AssertionError("Cannot test UPDATE without INSERT"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_update_", getCreateTableDefaultDefinition())) { + try (TestTable table = newTrinoTable("test_update_", getCreateTableDefaultDefinition())) { assertUpdate("INSERT INTO " + table.getName() + " (a, b) SELECT regionkey, regionkey * 2.5 FROM region", "SELECT count(*) FROM region"); assertThat(query("SELECT a, b FROM " + table.getName())) .matches(expectedValues("(0, 0.0), (1, 2.5), (2, 5.0), (3, 7.5), (4, 10.0)")); @@ -344,7 +344,7 @@ public void testMerge() throw new AssertionError("Cannot test MERGE without INSERT"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_merge_", getCreateTableDefaultDefinition())) { + try (TestTable table = newTrinoTable("test_merge_", getCreateTableDefaultDefinition())) { assertUpdate("INSERT INTO " + table.getName() + " (a, b) SELECT regionkey, regionkey * 2.5 FROM region", "SELECT count(*) FROM region"); assertThat(query("SELECT a, b FROM " + table.getName())) .matches(expectedValues("(0, 0.0), (1, 2.5), (2, 5.0), (3, 7.5), (4, 10.0)")); 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 e53c808fa9bf..1cba708dc25f 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 @@ -355,8 +355,7 @@ public void testCharVarcharComparison() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_char_varchar", "(k, v) AS VALUES" + " (-1, CAST(NULL AS char(3))), " + @@ -389,8 +388,7 @@ public void testVarcharCharComparison() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_varchar_char", "(k, v) AS VALUES" + " (-1, CAST(NULL AS varchar(3))), " + @@ -589,8 +587,7 @@ public void testVarcharCastToDateInPredicate() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "varchar_as_date_pred", "(a varchar)", List.of( @@ -611,8 +608,7 @@ public void testVarcharCastToDateInPredicate() } } - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "varchar_as_date_pred", "(a varchar)", List.of("'2005-06-bad-date'", "'2005-09-10'"))) { @@ -642,8 +638,7 @@ public void testVarcharCastToDateInPredicate() failureAssert -> failureAssert .hasMessage("Value cannot be cast to date: 2005-06-bad-date")); } - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "varchar_as_date_pred", "(a varchar)", List.of("'2005-09-10'"))) { @@ -1300,8 +1295,7 @@ public void testMaterializedViewGracePeriod() return; } - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_base_table", "AS TABLE region")) { Session defaultSession = getSession(); @@ -2277,7 +2271,7 @@ public void testAddColumn() } String tableName; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_column_", tableDefinitionForAddColumn())) { + try (TestTable table = newTrinoTable("test_add_column_", tableDefinitionForAddColumn())) { tableName = table.getName(); assertUpdate("INSERT INTO " + table.getName() + " SELECT 'first'", 1); assertQueryFails("ALTER TABLE " + table.getName() + " ADD COLUMN x bigint", ".* Column 'x' already exists"); @@ -2337,7 +2331,7 @@ public void testAddColumnWithComment() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_col_desc_", "(a_varchar varchar)")) { + try (TestTable table = newTrinoTable("test_add_col_desc_", "(a_varchar varchar)")) { String tableName = table.getName(); assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN b_varchar varchar COMMENT 'test new column comment'"); @@ -2353,7 +2347,7 @@ public void testAddNotNullColumnToEmptyTable() { skipTestUnless(hasBehavior(SUPPORTS_ADD_COLUMN)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_nn_to_empty", "(a_varchar varchar)")) { + try (TestTable table = newTrinoTable("test_add_nn_to_empty", "(a_varchar varchar)")) { String tableName = table.getName(); String addNonNullColumn = "ALTER TABLE " + tableName + " ADD COLUMN b_varchar varchar NOT NULL"; @@ -2380,7 +2374,7 @@ public void testAddNotNullColumn() { skipTestUnless(hasBehavior(SUPPORTS_ADD_COLUMN_NOT_NULL_CONSTRAINT)); // covered by testAddNotNullColumnToEmptyTable - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_nn_col", "(a_varchar varchar)")) { + try (TestTable table = newTrinoTable("test_add_nn_col", "(a_varchar varchar)")) { String tableName = table.getName(); assertUpdate("INSERT INTO " + tableName + " VALUES ('a')", 1); @@ -2424,7 +2418,7 @@ public void testAddRowField() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ROW_TYPE)); if (!hasBehavior(SUPPORTS_ADD_FIELD)) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_field_", "AS SELECT CAST(row(1) AS row(x integer)) AS col")) { + try (TestTable table = newTrinoTable("test_add_field_", "AS SELECT CAST(row(1) AS row(x integer)) AS col")) { assertQueryFails( "ALTER TABLE " + table.getName() + " ADD COLUMN col.y integer", "This connector does not support adding fields"); @@ -2432,7 +2426,7 @@ public void testAddRowField() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_add_field_", "AS SELECT CAST(row(1, row(10)) AS row(a integer, b row(x integer))) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, b row(x integer))"); @@ -2462,7 +2456,7 @@ public void testAddRowFieldInArray() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ROW_TYPE)); if (!hasBehavior(SUPPORTS_ADD_FIELD_IN_ARRAY)) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_field_in_array_", "AS SELECT CAST(array[row(1)] AS array(row(x integer))) AS col")) { + try (TestTable table = newTrinoTable("test_add_field_in_array_", "AS SELECT CAST(array[row(1)] AS array(row(x integer))) AS col")) { assertQueryFails( "ALTER TABLE " + table.getName() + " ADD COLUMN col.element.y integer", ".*does not support.*"); @@ -2470,7 +2464,7 @@ public void testAddRowFieldInArray() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_add_field_in_array_", "AS SELECT CAST(array[row(1, row(10), array[row(11)])] AS array(row(a integer, b row(x integer), c array(row(v integer))))) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("array(row(a integer, b row(x integer), c array(row(v integer))))"); @@ -2506,7 +2500,7 @@ public void testAddRowFieldInArray() } // test row in array of arrays - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_add_field_in_array_nested_", "AS SELECT CAST(array[array[row(1, row(10), array[row(11)])]] AS array(array(row(a integer, b row(x integer), c array(row(v integer)))))) AS col")) { @@ -2537,7 +2531,7 @@ public void testDropColumn() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); String tableName; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_column_", "AS SELECT 123 x, 456 y, 111 a")) { + try (TestTable table = newTrinoTable("test_drop_column_", "AS SELECT 123 x, 456 y, 111 a")) { tableName = table.getName(); assertUpdate("ALTER TABLE " + tableName + " DROP COLUMN x"); assertUpdate("ALTER TABLE " + tableName + " DROP COLUMN IF EXISTS y"); @@ -2561,7 +2555,7 @@ public void testDropRowField() if (!hasBehavior(SUPPORTS_DROP_COLUMN) || !hasBehavior(SUPPORTS_ROW_TYPE)) { return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_field_", "AS SELECT CAST(row(1, 2) AS row(x integer, y integer)) AS col")) { + try (TestTable table = newTrinoTable("test_drop_field_", "AS SELECT CAST(row(1, 2) AS row(x integer, y integer)) AS col")) { assertQueryFails( "ALTER TABLE " + table.getName() + " DROP COLUMN col.x", "This connector does not support dropping fields"); @@ -2569,7 +2563,7 @@ public void testDropRowField() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "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")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, b integer, c row(x integer, y integer))"); @@ -2610,7 +2604,7 @@ public void testDropRowFieldInArray() if (!hasBehavior(SUPPORTS_DROP_COLUMN) || !hasBehavior(SUPPORTS_ROW_TYPE)) { return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_field_in_array_", "AS SELECT CAST(array[row(1, 2)] AS array(row(x integer, y integer))) AS col")) { + try (TestTable table = newTrinoTable("test_drop_field_in_array_", "AS SELECT CAST(array[row(1, 2)] AS array(row(x integer, y integer))) AS col")) { assertQueryFails( "ALTER TABLE " + table.getName() + " DROP COLUMN col.element.x", ".*does not support.*"); @@ -2618,7 +2612,7 @@ public void testDropRowFieldInArray() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_drop_field_in_array_", "AS SELECT CAST(array[row(1, 2, row(10, 20), array[row(30, 40)])] AS array(row(a integer, b integer, c row(x integer, y integer), d array(row(v integer, w integer))))) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("array(row(a integer, b integer, c row(x integer, y integer), d array(row(v integer, w integer))))"); @@ -2672,7 +2666,7 @@ public void testDropRowFieldInArray() assertThat(getColumnType(table.getName(), "col")).isEqualTo("array(row(a integer))"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_drop_field_in_array_nested_", "AS SELECT CAST(array[array[row(1, 2, row(10, 20), array[row(30, 40)])]] AS array(array(row(a integer, b integer, c row(x integer, y integer), d array(row(v integer, w integer)))))) AS col")) { @@ -2705,7 +2699,7 @@ public void testDropRowFieldWhenDuplicates() { skipTestUnless(hasBehavior(SUPPORTS_DROP_FIELD)); - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_drop_duplicated_field_", "AS SELECT CAST(row(1, 2, 3) AS row(a integer, a integer, b integer)) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, a integer, b integer)"); @@ -2722,7 +2716,7 @@ public void testDropRowFieldCaseSensitivity() { skipTestUnless(hasBehavior(SUPPORTS_DROP_FIELD)); - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_drop_row_field_case_sensitivity_", "AS SELECT CAST(row(1, 2) AS row(lower integer, \"UPPER\" integer)) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(lower integer, UPPER integer)"); @@ -2745,7 +2739,7 @@ public void testDropAmbiguousRowFieldCaseSensitivity() { skipTestUnless(hasBehavior(SUPPORTS_DROP_FIELD)); - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_drop_row_field_case_sensitivity_", """ AS SELECT CAST(row(1, 2, 3, 4, 5) AS @@ -2771,7 +2765,7 @@ public void testDropAndAddColumnWithSameName() { skipTestUnless(hasBehavior(SUPPORTS_DROP_COLUMN) && hasBehavior(SUPPORTS_ADD_COLUMN)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_add_column", "AS SELECT 1 x, 2 y, 3 z")) { + try (TestTable table = newTrinoTable("test_drop_add_column", "AS SELECT 1 x, 2 y, 3 z")) { assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN y"); assertQuery("SELECT * FROM " + table.getName(), "VALUES (1, 3)"); @@ -2791,7 +2785,7 @@ public void testRenameColumn() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); String tableName; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_rename_column_", "AS SELECT 'some value' x")) { + try (TestTable table = newTrinoTable("test_rename_column_", "AS SELECT 'some value' x")) { tableName = table.getName(); assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN x TO before_y"); assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN IF EXISTS before_y TO y"); @@ -2823,7 +2817,7 @@ public void testRenameColumnWithComment() { skipTestUnless(hasBehavior(SUPPORTS_RENAME_COLUMN) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_rename_column_", "(col INT COMMENT 'test column comment')")) { + try (TestTable table = newTrinoTable("test_rename_column_", "(col INT COMMENT 'test column comment')")) { assertThat(getColumnComment(table.getName(), "col")).isEqualTo("test column comment"); assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN col TO renamed_col"); @@ -2837,7 +2831,7 @@ public void testRenameRowField() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ROW_TYPE)); if (!hasBehavior(SUPPORTS_RENAME_FIELD)) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_rename_field_", "AS SELECT CAST(row(1) AS row(x integer)) AS col")) { + try (TestTable table = newTrinoTable("test_rename_field_", "AS SELECT CAST(row(1) AS row(x integer)) AS col")) { assertQueryFails( "ALTER TABLE " + table.getName() + " RENAME COLUMN col.x TO x_renamed", "This connector does not support renaming fields"); @@ -2845,7 +2839,7 @@ public void testRenameRowField() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_add_field_", "AS SELECT CAST(row(1, row(10)) AS row(a integer, b row(x integer))) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, b row(x integer))"); @@ -2874,7 +2868,7 @@ public void testRenameRowFieldCaseSensitivity() { skipTestUnless(hasBehavior(SUPPORTS_RENAME_FIELD)); - try (TestTable table = new TestTable(getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_add_row_field_case_sensitivity_", "AS SELECT CAST(row(1, 2) AS row(lower integer, \"UPPER\" integer)) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(lower integer, UPPER integer)"); @@ -2903,7 +2897,7 @@ public void testSetColumnType() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_column_type_", "AS SELECT CAST(123 AS integer) AS col")) { + try (TestTable table = newTrinoTable("test_set_column_type_", "AS SELECT CAST(123 AS integer) AS col")) { assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE bigint"); assertThat(getColumnType(table.getName(), "col")).isEqualTo("bigint"); @@ -2921,7 +2915,7 @@ public void testSetColumnTypes() 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"); + table = newTrinoTable("test_set_column_type_", " AS SELECT CAST(" + setup.sourceValueLiteral + " AS " + setup.sourceColumnType + ") AS col"); } catch (Exception e) { verifyUnsupportedTypeException(e, setup.sourceColumnType); @@ -3043,7 +3037,7 @@ 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)")) { + try (TestTable table = newTrinoTable("test_set_column_type_null_", "(col int NOT NULL)")) { assertThat(columnIsNullable(table.getName(), "col")).isFalse(); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE bigint"); @@ -3056,7 +3050,7 @@ 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')")) { + try (TestTable table = newTrinoTable("test_set_column_type_comment_", "(col int COMMENT 'test comment')")) { assertThat(getColumnComment(table.getName(), "col")).isEqualTo("test comment"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE bigint"); @@ -3082,7 +3076,7 @@ public void testSetColumnIncompatibleType() { skipTestUnless(hasBehavior(SUPPORTS_SET_COLUMN_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_invalid_column_type_", "AS SELECT 'test' AS col")) { + try (TestTable table = newTrinoTable("test_set_invalid_column_type_", "AS SELECT 'test' AS col")) { assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE integer")) .satisfies(this::verifySetColumnTypeFailurePermissible); } @@ -3093,7 +3087,7 @@ public void testSetColumnOutOfRangeType() { skipTestUnless(hasBehavior(SUPPORTS_SET_COLUMN_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_column_type_invalid_range_", "AS SELECT CAST(9223372036854775807 AS bigint) AS col")) { + try (TestTable table = newTrinoTable("test_set_column_type_invalid_range_", "AS SELECT CAST(9223372036854775807 AS bigint) AS col")) { assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE integer")) .satisfies(this::verifySetColumnTypeFailurePermissible); } @@ -3110,7 +3104,7 @@ public void testSetFieldType() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ROW_TYPE)); if (!hasBehavior(SUPPORTS_SET_FIELD_TYPE)) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_", "(col row(field int))")) { + try (TestTable table = newTrinoTable("test_set_field_type_", "(col row(field int))")) { assertQueryFails( "ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE bigint", "This connector does not support setting field types"); @@ -3118,7 +3112,7 @@ public void testSetFieldType() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_", "AS SELECT CAST(row(123) AS row(field integer)) AS col")) { + try (TestTable table = newTrinoTable("test_set_field_type_", "AS SELECT CAST(row(123) AS row(field integer)) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(field integer)"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE bigint"); @@ -3138,8 +3132,7 @@ public void testSetFieldTypes() for (SetColumnTypeSetup setup : setFieldTypesDataProvider()) { TestTable table; try { - table = new TestTable( - getQueryRunner()::execute, + table = newTrinoTable( "test_set_field_type_", " AS SELECT CAST(row(" + setup.sourceValueLiteral + ") AS row(field " + setup.sourceColumnType + ")) AS col"); } @@ -3182,7 +3175,7 @@ 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")) { + try (TestTable table = newTrinoTable("test_set_field_type_case_", " AS SELECT CAST(row(1) AS row(\"UPPER\" integer)) col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(UPPER integer)"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.upper SET DATA TYPE bigint"); @@ -3197,7 +3190,7 @@ 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)")) { + try (TestTable table = newTrinoTable("test_set_field_type_null_", "(col row(field int) NOT NULL)")) { assertThat(columnIsNullable(table.getName(), "col")).isFalse(); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE bigint"); @@ -3210,7 +3203,7 @@ 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')")) { + try (TestTable table = newTrinoTable("test_set_field_type_comment_", "(col row(field int) COMMENT 'test comment')")) { assertThat(getColumnComment(table.getName(), "col")).isEqualTo("test comment"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE bigint"); @@ -3223,8 +3216,7 @@ public void testSetFieldIncompatibleType() { skipTestUnless(hasBehavior(SUPPORTS_SET_FIELD_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_set_invalid_field_type_", "(row_col row(field varchar), nested_col row(field row(nested int)))")) { assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN row_col.field SET DATA TYPE row(nested integer)")) @@ -3241,8 +3233,7 @@ public void testSetFieldOutOfRangeType() { skipTestUnless(hasBehavior(SUPPORTS_SET_FIELD_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_set_field_type_invalid_range_", "AS SELECT CAST(row(9223372036854775807) AS row(field bigint)) AS col")) { assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE integer")) @@ -3256,7 +3247,7 @@ public void testSetFieldTypeInArray() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ARRAY) && hasBehavior(SUPPORTS_ROW_TYPE)); if (!hasBehavior(SUPPORTS_SET_FIELD_TYPE_IN_ARRAY)) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_in_array_", "(col array(row(field int)))")) { + try (TestTable table = newTrinoTable("test_set_field_type_in_array_", "(col array(row(field int)))")) { assertQueryFails( "ALTER TABLE " + table.getName() + " ALTER COLUMN col.element.field SET DATA TYPE bigint", ".*does not support.*"); @@ -3264,7 +3255,7 @@ public void testSetFieldTypeInArray() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_in_array_", "AS SELECT CAST(array[row(123)] AS array(row(field integer))) AS col")) { + try (TestTable table = newTrinoTable("test_set_field_type_in_array_", "AS SELECT CAST(array[row(123)] AS array(row(field integer))) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("array(row(field integer))"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.element.field SET DATA TYPE bigint"); @@ -3281,7 +3272,7 @@ public void testSetFieldTypeInNestedArray() { skipTestUnless(hasBehavior(SUPPORTS_SET_FIELD_TYPE_IN_ARRAY) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ARRAY) && hasBehavior(SUPPORTS_ROW_TYPE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_in_nested_array_", "AS SELECT CAST(array[array[row(array[row(123)])]] AS array(array(row(field array(row(a integer)))))) AS col")) { + try (TestTable table = newTrinoTable("test_set_field_type_in_nested_array_", "AS SELECT CAST(array[array[row(array[row(123)])]] AS array(array(row(field array(row(a integer)))))) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("array(array(row(field array(row(a integer)))))"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.element.element.field.element.a SET DATA TYPE bigint"); @@ -3304,13 +3295,13 @@ public void testSetFieldMapKeyType() String tableDefinition = "AS SELECT CAST(map(array[row(1)], array[2]) AS map(row(field integer), integer)) AS col"; if (!hasBehavior(SUPPORTS_SET_FIELD_TYPE_IN_MAP)) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_in_map", tableDefinition)) { + try (TestTable table = newTrinoTable("test_set_field_type_in_map", tableDefinition)) { assertQueryFails("ALTER TABLE " + table.getName() + " ALTER COLUMN col.key.field SET DATA TYPE bigint", ".*does not support.*"); } return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_in_map", tableDefinition)) { + try (TestTable table = newTrinoTable("test_set_field_type_in_map", tableDefinition)) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("map(row(field integer), integer)"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.key.field SET DATA TYPE bigint"); @@ -3328,13 +3319,13 @@ public void testSetFieldMapValueType() String tableDefinition = "AS SELECT CAST(map(array[1], array[row(2)]) AS map(integer, row(field integer))) AS col"; if (!hasBehavior(SUPPORTS_SET_FIELD_TYPE_IN_MAP)) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_in_map", tableDefinition)) { + try (TestTable table = newTrinoTable("test_set_field_type_in_map", tableDefinition)) { assertQueryFails("ALTER TABLE " + table.getName() + " ALTER COLUMN col.value.field SET DATA TYPE bigint", ".*does not support.*"); } return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_in_map", tableDefinition)) { + try (TestTable table = newTrinoTable("test_set_field_type_in_map", tableDefinition)) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("map(integer, row(field integer))"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.value.field SET DATA TYPE bigint"); @@ -3350,8 +3341,7 @@ public void testSetNestedFieldMapKeyType() { skipTestUnless(hasBehavior(SUPPORTS_SET_FIELD_TYPE_IN_ARRAY) && hasBehavior(SUPPORTS_SET_FIELD_TYPE_IN_MAP) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ARRAY) && hasBehavior(SUPPORTS_MAP_TYPE) && hasBehavior(SUPPORTS_ROW_TYPE)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_set_nested_field_type_in_map", "AS SELECT CAST(array[map(array[row(1)], array[2])] AS array(map(row(field integer), integer))) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("array(map(row(field integer), integer))"); @@ -3369,8 +3359,7 @@ public void testSetNestedFieldMapValueType() { skipTestUnless(hasBehavior(SUPPORTS_SET_FIELD_TYPE_IN_ARRAY) && hasBehavior(SUPPORTS_SET_FIELD_TYPE_IN_MAP) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ARRAY) && hasBehavior(SUPPORTS_MAP_TYPE) && hasBehavior(SUPPORTS_ROW_TYPE)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_set_nested_field_type_in_map", "AS SELECT CAST(array[map(array[1], array[row(2)])] AS array(map(integer, row(field integer)))) AS col")) { assertThat(getColumnType(table.getName(), "col")).isEqualTo("array(map(integer, row(field integer)))"); @@ -3401,7 +3390,7 @@ public void testDropNotNullConstraint() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_NOT_NULL_CONSTRAINT)); if (!hasBehavior(SUPPORTS_DROP_NOT_NULL_CONSTRAINT)) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_not_null_", "(col integer NOT NULL)")) { + try (TestTable table = newTrinoTable("test_drop_not_null_", "(col integer NOT NULL)")) { assertQueryFails( "ALTER TABLE " + table.getName() + " ALTER COLUMN col DROP NOT NULL", "This connector does not support dropping a not null constraint"); @@ -3409,7 +3398,7 @@ public void testDropNotNullConstraint() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_not_null_", "(col integer NOT NULL)")) { + try (TestTable table = newTrinoTable("test_drop_not_null_", "(col integer NOT NULL)")) { assertThat(columnIsNullable(table.getName(), "col")).isFalse(); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col DROP NOT NULL"); @@ -3426,7 +3415,7 @@ public void testDropNotNullConstraintWithColumnComment() skipTestUnless(hasBehavior(SUPPORTS_DROP_NOT_NULL_CONSTRAINT) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT)); // Verify DROP NOT NULL preserves the existing column comment - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_not_null_", "(col integer NOT NULL COMMENT 'test comment')")) { + try (TestTable table = newTrinoTable("test_drop_not_null_", "(col integer NOT NULL COMMENT 'test comment')")) { assertThat(getColumnComment(table.getName(), "col")).isEqualTo("test comment"); assertThat(columnIsNullable(table.getName(), "col")).isFalse(); @@ -3557,7 +3546,7 @@ public void testCreateOrReplaceTableWhenTableAlreadyExistsSameSchema() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_", "AS SELECT CAST(1 AS BIGINT) AS nationkey, 'test' AS name, CAST(2 AS BIGINT) AS regionkey FROM nation LIMIT 1")) { + try (TestTable table = newTrinoTable("test_create_or_replace_", "AS SELECT CAST(1 AS BIGINT) AS nationkey, 'test' AS name, CAST(2 AS BIGINT) AS regionkey FROM nation LIMIT 1")) { @Language("SQL") String query = "SELECT nationkey, name, regionkey FROM nation"; @Language("SQL") String rowCountQuery = "SELECT count(*) FROM nation"; assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + " AS " + query, rowCountQuery); @@ -3574,7 +3563,7 @@ public void testCreateOrReplaceTableWhenTableAlreadyExistsSameSchemaNoData() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_", " AS SELECT nationkey, name, regionkey FROM nation")) { + try (TestTable table = newTrinoTable("test_create_or_replace_", " AS SELECT nationkey, name, regionkey FROM nation")) { assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + " AS SELECT nationkey, name, regionkey FROM nation WITH NO DATA", 0L); assertQueryReturnsEmptyResult("SELECT * FROM " + table.getName()); } @@ -3589,7 +3578,7 @@ public void testCreateOrReplaceTableWithNewColumnNames() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_", " AS SELECT nationkey, name, regionkey FROM nation")) { + try (TestTable table = newTrinoTable("test_create_or_replace_", " AS SELECT nationkey, name, regionkey FROM nation")) { assertTableColumnNames(table.getName(), "nationkey", "name", "regionkey"); @Language("SQL") String query = "SELECT nationkey AS nationkey_new, name AS name_new_2, regionkey AS region_key_new FROM nation"; @Language("SQL") String rowCountQuery = "SELECT count(*) FROM nation"; @@ -3608,7 +3597,7 @@ public void testCreateOrReplaceTableWithDifferentDataType() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_or_replace_", " AS SELECT nationkey, name FROM nation")) { + try (TestTable table = newTrinoTable("test_create_or_replace_", " AS SELECT nationkey, name FROM nation")) { @Language("SQL") String query = "SELECT name AS nationkey, nationkey AS name FROM nation"; @Language("SQL") String rowCountQuery = "SELECT count(*) FROM nation"; assertUpdate("CREATE OR REPLACE TABLE " + table.getName() + " AS " + query, rowCountQuery); @@ -4218,7 +4207,7 @@ public void testCommentTable() String catalogName = getSession().getCatalog().orElseThrow(); String schemaName = getSession().getSchema().orElseThrow(); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_comment_", "(a integer)")) { + try (TestTable table = newTrinoTable("test_comment_", "(a integer)")) { // comment initially not set assertThat(getTableComment(catalogName, schemaName, table.getName())).isEqualTo(null); @@ -4311,7 +4300,7 @@ public void testCommentColumn() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_comment_column_", "(a integer)")) { + try (TestTable table = newTrinoTable("test_comment_column_", "(a integer)")) { // comment set assertUpdate("COMMENT ON COLUMN " + table.getName() + ".a IS 'new comment'"); assertThat((String) computeScalar("SHOW CREATE TABLE " + table.getName())).contains("COMMENT 'new comment'"); @@ -4346,7 +4335,7 @@ protected void testCommentColumnName(String columnName, boolean delimited) String nameInSql = toColumnNameInSql(columnName, delimited); // TODO (https://github.com/trinodb/trino/issues/5901) Use longer table name once Oracle version is updated - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_comment_column", "(" + nameInSql + " integer)")) { + try (TestTable table = newTrinoTable("test_comment_column", "(" + nameInSql + " integer)")) { assertUpdate("COMMENT ON COLUMN " + table.getName() + "." + nameInSql + " IS 'test comment'"); assertThat(getColumnComment(table.getName(), columnName.replace("'", "''").toLowerCase(ENGLISH))).isEqualTo("test comment"); } @@ -4416,7 +4405,7 @@ public void testInsert() String query = "SELECT name, nationkey, regionkey FROM nation"; - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_", "AS " + query + " WITH NO DATA")) { + try (TestTable table = newTrinoTable("test_insert_", "AS " + query + " WITH NO DATA")) { assertQuery("SELECT count(*) FROM " + table.getName() + "", "SELECT 0"); assertUpdate("INSERT INTO " + table.getName() + " " + query, 25); @@ -4477,13 +4466,13 @@ public void testInsertUnicode() throw new AssertionError("Cannot test INSERT without CREATE TABLE, the test needs to be implemented in a connector-specific way"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_unicode_", "(test varchar(50))")) { + try (TestTable table = newTrinoTable("test_insert_unicode_", "(test varchar(50))")) { assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'Hello', U&'hello\\6d4B\\8Bd5world\\7F16\\7801' ", 2); assertThat(computeActual("SELECT test FROM " + table.getName()).getOnlyColumnAsSet()) .containsExactlyInAnyOrder("Hello", "hello测试world编码"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_unicode_", "(test varchar(50))")) { + try (TestTable table = newTrinoTable("test_insert_unicode_", "(test varchar(50))")) { assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'aa', 'bé'", 2); assertQuery("SELECT test FROM " + table.getName(), "VALUES 'aa', 'bé'"); assertQuery("SELECT test FROM " + table.getName() + " WHERE test = 'aa'", "VALUES 'aa'"); @@ -4492,7 +4481,7 @@ public void testInsertUnicode() assertQueryReturnsEmptyResult("SELECT test FROM " + table.getName() + " WHERE test = 'ba'"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_unicode_", "(test varchar(50))")) { + try (TestTable table = newTrinoTable("test_insert_unicode_", "(test varchar(50))")) { assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'a', 'é'", 2); assertQuery("SELECT test FROM " + table.getName(), "VALUES 'a', 'é'"); assertQuery("SELECT test FROM " + table.getName() + " WHERE test = 'a'", "VALUES 'a'"); @@ -4510,7 +4499,7 @@ public void testInsertHighestUnicodeCharacter() throw new AssertionError("Cannot test INSERT without CREATE TABLE, the test needs to be implemented in a connector-specific way"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_unicode_", "(test varchar(50))")) { + try (TestTable table = newTrinoTable("test_insert_unicode_", "(test varchar(50))")) { assertUpdate("INSERT INTO " + table.getName() + "(test) VALUES 'Hello', U&'hello\\6d4B\\8Bd5\\+10FFFFworld\\7F16\\7801' ", 2); assertThat(computeActual("SELECT test FROM " + table.getName()).getOnlyColumnAsSet()) .containsExactlyInAnyOrder("Hello", "hello测试􏿿world编码"); @@ -4533,7 +4522,7 @@ public void testInsertArray() abort("not supported"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_array_", "(a ARRAY, b ARRAY)")) { + try (TestTable table = newTrinoTable("test_insert_array_", "(a ARRAY, b ARRAY)")) { assertUpdate("INSERT INTO " + table.getName() + " (a) VALUES (ARRAY[null])", 1); assertUpdate("INSERT INTO " + table.getName() + " (a, b) VALUES (ARRAY[1.23E1], ARRAY[1.23E1])", 1); assertQuery("SELECT a[1], b[1] FROM " + table.getName(), "VALUES (null, null), (12.3, 12)"); @@ -4553,7 +4542,7 @@ public void testInsertMap() abort("not supported"); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_map_", "(col map(integer, integer))")) { + try (TestTable table = newTrinoTable("test_insert_map_", "(col map(integer, integer))")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES map(ARRAY[1], ARRAY[2])", 1); assertThat(query("SELECT * FROM " + table.getName())) .matches("VALUES map(ARRAY[1], ARRAY[2])"); @@ -4566,8 +4555,7 @@ public void testInsertSameValues() skipTestUnless(hasBehavior(SUPPORTS_INSERT)); skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "insert_same_values", "AS " + join(" UNION ALL ", nCopies(2, "SELECT * FROM region")))) { assertQuery("SELECT count(*) FROM " + table.getName(), "VALUES 10"); @@ -4585,13 +4573,13 @@ public void testInsertNegativeDate() throw new AssertionError("Cannot test INSERT negative dates without CREATE TABLE, the test needs to be implemented in a connector-specific way"); } if (!hasBehavior(SUPPORTS_NEGATIVE_DATE)) { - try (TestTable table = new TestTable(getQueryRunner()::execute, "insert_date", "(dt DATE)")) { + try (TestTable table = newTrinoTable("insert_date", "(dt DATE)")) { assertQueryFails(format("INSERT INTO %s VALUES (DATE '-0001-01-01')", table.getName()), errorMessageForInsertNegativeDate("-0001-01-01")); } return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "insert_date", "(dt DATE)")) { + try (TestTable table = newTrinoTable("insert_date", "(dt DATE)")) { assertUpdate(format("INSERT INTO %s VALUES (DATE '-0001-01-01')", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES DATE '-0001-01-01'"); assertQuery(format("SELECT * FROM %s WHERE dt = DATE '-0001-01-01'", table.getName()), "VALUES DATE '-0001-01-01'"); @@ -4616,7 +4604,7 @@ public void testInsertIntoNotNullColumn() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "insert_not_null", "(nullable_col INTEGER, not_null_col INTEGER NOT NULL)")) { + try (TestTable table = newTrinoTable("insert_not_null", "(nullable_col INTEGER, not_null_col INTEGER NOT NULL)")) { assertUpdate(format("INSERT INTO %s (not_null_col) VALUES (2)", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES (NULL, 2)"); // The error message comes from remote databases when ConnectorMetadata.supportsMissingColumnsOnInsert is true @@ -4628,7 +4616,7 @@ public void testInsertIntoNotNullColumn() assertUpdate(format("INSERT INTO %s (nullable_col) SELECT nationkey FROM nation WHERE regionkey < 0", table.getName()), 0); } - try (TestTable table = new TestTable(getQueryRunner()::execute, "commuted_not_null", "(nullable_col BIGINT, not_null_col BIGINT NOT NULL)")) { + try (TestTable table = newTrinoTable("commuted_not_null", "(nullable_col BIGINT, not_null_col BIGINT NOT NULL)")) { assertUpdate(format("INSERT INTO %s (not_null_col) VALUES (2)", table.getName()), 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES (NULL, 2)"); // This is enforced by the engine and not the connector @@ -4669,8 +4657,7 @@ public void testInsertInTransaction() skipTestUnless(hasBehavior(SUPPORTS_INSERT)); skipTestUnless(hasBehavior(SUPPORTS_MULTI_STATEMENT_WRITES)); // covered by testWriteNotAllowedInTransaction - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_tx_insert", "(a bigint)")) { String tableName = table.getName(); @@ -4688,8 +4675,7 @@ public void testSelectAfterInsertInTransaction() return; } - try (TestTable table = new TestTable( - getQueryRunner()::execute, + try (TestTable table = newTrinoTable( "test_insert_select_", "AS SELECT nationkey, name, regionkey FROM nation WHERE nationkey = 1")) { String tableName = table.getName(); @@ -4733,7 +4719,7 @@ public void testDelete() skipTestUnless(hasBehavior(SUPPORTS_DELETE)); // delete successive parts of the table - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_", "AS SELECT * FROM orders")) { + try (TestTable table = newTrinoTable("test_delete_", "AS SELECT * FROM orders")) { assertUpdate("DELETE FROM " + table.getName() + " WHERE custkey <= 100", "SELECT count(*) FROM orders WHERE custkey <= 100"); assertQuery("SELECT * FROM " + table.getName(), "SELECT * FROM orders WHERE custkey > 100"); @@ -4745,12 +4731,12 @@ public void testDelete() } // delete without matching any rows - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_", "AS SELECT * FROM orders")) { + try (TestTable table = newTrinoTable("test_delete_", "AS SELECT * FROM orders")) { assertUpdate("DELETE FROM " + table.getName() + " WHERE orderkey < 0", 0); } // delete with a predicate that optimizes to false - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_", "AS SELECT * FROM orders")) { + try (TestTable table = newTrinoTable("test_delete_", "AS SELECT * FROM orders")) { assertUpdate("DELETE FROM " + table.getName() + " WHERE orderkey > 5 AND orderkey < 4", 0); } @@ -4776,7 +4762,7 @@ public void testDeleteWithLike() { skipTestUnless(hasBehavior(SUPPORTS_DELETE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_with_like_", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_with_like_", "AS SELECT * FROM nation")) { assertUpdate("DELETE FROM " + table.getName() + " WHERE name LIKE '%a%'", "VALUES 0"); assertUpdate("DELETE FROM " + table.getName() + " WHERE name LIKE '%A%'", "SELECT count(*) FROM nation WHERE name LIKE '%A%'"); } @@ -4904,7 +4890,7 @@ public void testDeleteWithVarcharPredicate() { skipTestUnless(hasBehavior(SUPPORTS_DELETE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_with_varchar_predicate_", "AS SELECT * FROM orders")) { + try (TestTable table = newTrinoTable("test_delete_with_varchar_predicate_", "AS SELECT * FROM orders")) { assertUpdate("DELETE FROM " + table.getName() + " WHERE orderstatus = 'O'", "SELECT count(*) FROM orders WHERE orderstatus = 'O'"); assertQuery("SELECT * FROM " + table.getName(), "SELECT * FROM orders WHERE orderstatus <> 'O'"); } @@ -4919,7 +4905,7 @@ public void verifySupportsDeleteDeclaration() } skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_supports_delete", "(regionkey int)")) { + try (TestTable table = newTrinoTable("test_supports_delete", "(regionkey int)")) { assertQueryFails("DELETE FROM " + table.getName(), MODIFYING_ROWS_MESSAGE); } } @@ -4933,7 +4919,7 @@ public void verifySupportsRowLevelDeleteDeclaration() } skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_supports_row_level_delete", "(regionkey int)")) { + try (TestTable table = newTrinoTable("test_supports_row_level_delete", "(regionkey int)")) { assertQueryFails("DELETE FROM " + table.getName() + " WHERE regionkey = 2", MODIFYING_ROWS_MESSAGE); } } @@ -4942,7 +4928,7 @@ public void verifySupportsRowLevelDeleteDeclaration() public void testDeleteAllDataFromTable() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_DELETE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_delete_all_data", "AS SELECT * FROM region")) { + try (TestTable table = newTrinoTable("test_delete_all_data", "AS SELECT * FROM region")) { // not using assertUpdate as some connectors provide update count and some not getQueryRunner().execute("DELETE FROM " + table.getName()); assertQuery("SELECT count(*) FROM " + table.getName(), "VALUES 0"); @@ -4954,7 +4940,7 @@ public void testRowLevelDelete() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ROW_LEVEL_DELETE)); // TODO (https://github.com/trinodb/trino/issues/5901) Use longer table name once Oracle version is updated - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_row_delete", "AS SELECT * FROM region")) { + try (TestTable table = newTrinoTable("test_row_delete", "AS SELECT * FROM region")) { assertUpdate("DELETE FROM " + table.getName() + " WHERE regionkey = 2", 1); assertQuery("SELECT count(*) FROM " + table.getName(), "VALUES 4"); } @@ -4969,7 +4955,7 @@ public void verifySupportsUpdateDeclaration() } skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_supports_update", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_supports_update", "AS SELECT * FROM nation")) { assertQueryFails("UPDATE " + table.getName() + " SET nationkey = 100 WHERE regionkey = 2", MODIFYING_ROWS_MESSAGE); } } @@ -4983,7 +4969,7 @@ public void verifySupportsRowLevelUpdateDeclaration() } skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_supports_update", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_supports_update", "AS SELECT * FROM nation")) { assertQueryFails("UPDATE " + table.getName() + " SET nationkey = nationkey * 100 WHERE regionkey = 2", MODIFYING_ROWS_MESSAGE); } } @@ -4996,7 +4982,7 @@ public void testUpdate() assertQueryFails("UPDATE nation SET nationkey = nationkey + regionkey WHERE regionkey < 1", MODIFYING_ROWS_MESSAGE); return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_row_update", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_row_update", "AS SELECT * FROM nation")) { assertUpdate("UPDATE " + table.getName() + " SET nationkey = 100 WHERE regionkey = 2", 5); assertQuery("SELECT count(*) FROM " + table.getName() + " WHERE nationkey = 100", "VALUES 5"); } @@ -5007,7 +4993,7 @@ public void testUpdateMultipleCondition() { skipTestUnless(hasBehavior(SUPPORTS_UPDATE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_row_update", "AS SELECT * FROM (VALUES (1, 10), (1, 20), (2, 10)) AS t(a, b)")) { + try (TestTable table = newTrinoTable("test_row_update", "AS SELECT * FROM (VALUES (1, 10), (1, 20), (2, 10)) AS t(a, b)")) { assertUpdate("UPDATE " + table.getName() + " SET b = 100 WHERE a = 1 AND b = 10", 1); assertQuery("SELECT * FROM " + table.getName(), "VALUES (1, 100), (1, 20), (2, 10)"); } @@ -5043,7 +5029,7 @@ public void testUpdateCaseSensitivity() { skipTestUnless(hasBehavior(SUPPORTS_UPDATE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_row_update", "AS SELECT * FROM nation")) { + try (TestTable table = newTrinoTable("test_row_update", "AS SELECT * FROM nation")) { assertUpdate("UPDATE " + table.getName() + " SET NATIONKEY = 100 WHERE REGIONKEY = 2", 5); assertQuery("SELECT count(*) FROM " + table.getName() + " WHERE nationkey = 100", "VALUES 5"); } @@ -5336,7 +5322,7 @@ public void testCreateOrReplaceTableConcurrently() protected TestTable createTableWithOneIntegerColumn(String namePrefix) { - return new TestTable(getQueryRunner()::execute, namePrefix, "(col integer)"); + return newTrinoTable(namePrefix, "(col integer)"); } @Test @@ -5375,7 +5361,7 @@ public void testUpdateRowType() return; } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_update_with_predicates_on_row_types", "(int_t INT, row_t ROW(f1 INT, f2 INT))")) { + try (TestTable table = newTrinoTable("test_update_with_predicates_on_row_types", "(int_t INT, row_t ROW(f1 INT, f2 INT))")) { String tableName = table.getName(); assertUpdate("INSERT INTO " + tableName + " VALUES (1, ROW(2, 3)), (11, ROW(12, 13)), (21, ROW(22, 23))", 3); assertUpdate("UPDATE " + tableName + " SET int_t = int_t - 1 WHERE row_t.f2 = 3", 1); @@ -5394,7 +5380,7 @@ public void testPredicateOnRowTypeField() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_INSERT) && hasBehavior(SUPPORTS_ROW_TYPE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_predicate_on_row_type_field", "(int_t INT, row_t row(varchar_t VARCHAR, int_t INT))")) { + try (TestTable table = newTrinoTable("test_predicate_on_row_type_field", "(int_t INT, row_t row(varchar_t VARCHAR, int_t INT))")) { assertUpdate("INSERT INTO " + table.getName() + " VALUES (2, row('first', 1)), (20, row('second', 10)), (200, row('third', 100))", 3); assertQuery("SELECT int_t FROM " + table.getName() + " WHERE row_t.int_t = 1", "VALUES 2"); assertQuery("SELECT int_t FROM " + table.getName() + " WHERE row_t.int_t > 1", "VALUES 20, 200"); @@ -5449,7 +5435,7 @@ public void testTruncateTable() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_truncate", "AS SELECT * FROM region")) { + try (TestTable table = newTrinoTable("test_truncate", "AS SELECT * FROM region")) { assertUpdate("TRUNCATE TABLE " + table.getName()); assertQuery("SELECT count(*) FROM " + table.getName(), "VALUES 0"); } @@ -5798,7 +5784,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))) { + try (TestTable table = newTrinoTable("test_create_", "(a bigint) COMMENT " + varcharLiteral(comment))) { assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), table.getName())).isEqualTo(comment); } } @@ -5821,7 +5807,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")) { + try (TestTable table = newTrinoTable("test_create_", " COMMENT " + varcharLiteral(comment) + " AS SELECT 1 a")) { assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), table.getName())).isEqualTo(comment); } } @@ -5844,7 +5830,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) + ")")) { + try (TestTable table = newTrinoTable("test_create_", " (a bigint COMMENT " + varcharLiteral(comment) + ")")) { assertThat(getColumnComment(table.getName(), "a")).isEqualTo(comment); } } @@ -5867,7 +5853,7 @@ protected void testAddColumnWithCommentSpecialCharacter(String comment) { skipTestUnless(hasBehavior(SUPPORTS_ADD_COLUMN_WITH_COMMENT)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_col_", "(a_varchar varchar)")) { + try (TestTable table = newTrinoTable("test_add_col_", "(a_varchar varchar)")) { assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN b_varchar varchar COMMENT " + varcharLiteral(comment)); assertThat(getColumnComment(table.getName(), "b_varchar")).isEqualTo(comment); } @@ -5891,7 +5877,7 @@ private void testCommentTableSpecialCharacter(String comment) { skipTestUnless(hasBehavior(SUPPORTS_COMMENT_ON_TABLE)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_comment_table_", "(a integer)")) { + try (TestTable table = newTrinoTable("test_comment_table_", "(a integer)")) { assertUpdate("COMMENT ON TABLE " + table.getName() + " IS " + varcharLiteral(comment)); assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), table.getName())).isEqualTo(comment); } @@ -5915,7 +5901,7 @@ private void testCommentColumnSpecialCharacter(String comment) { skipTestUnless(hasBehavior(SUPPORTS_COMMENT_ON_COLUMN)); - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_comment_column_", "(a integer)")) { + try (TestTable table = newTrinoTable("test_comment_column_", "(a integer)")) { assertUpdate("COMMENT ON COLUMN " + table.getName() + ".a IS " + varcharLiteral(comment)); assertThat(getColumnComment(table.getName(), "a")).isEqualTo(comment); } @@ -6081,8 +6067,7 @@ public void testTimestampWithTimeZoneCastToDatePredicate() TestTable table; try { - table = new TestTable( - getQueryRunner()::execute, + table = newTrinoTable( "timestamptz_to_date", // These to timestamps are same local time, but different point in times and also different date at UTC time zone """ @@ -6112,8 +6097,7 @@ public void testTimestampWithTimeZoneCastToTimestampPredicate() TestTable table; try { - table = new TestTable( - getQueryRunner()::execute, + table = newTrinoTable( "timestamptz_to_ts", // These to timestamps are same local time, but different point in times """ @@ -6801,6 +6785,18 @@ public void testMergeAllColumnsReversed() assertUpdate("DROP TABLE " + targetTable); } + @Test + protected void testUpdateWithSubquery() + { + skipTestUnless(hasBehavior(SUPPORTS_MERGE)); + + try (TestTable table = createTestTableForWrites("test_update_with_subquery", " AS SELECT * FROM orders", "orderkey")) { + assertQuery("SELECT count(*) FROM " + table.getName() + " WHERE shippriority = 101 AND custkey = (SELECT min(custkey) FROM customer)", "VALUES 0"); + assertUpdate("UPDATE " + table.getName() + " SET shippriority = 101 WHERE custkey = (SELECT min(custkey) FROM customer)", 9); + assertQuery("SELECT count(*) FROM " + table.getName() + " WHERE shippriority = 101 AND custkey = (SELECT min(custkey) FROM customer)", "VALUES 9"); + } + } + private void verifyUnsupportedTypeException(Throwable exception, String trinoTypeName) { String typeNameBase = trinoTypeName.replaceFirst("\\(.*", ""); @@ -7026,8 +7022,7 @@ public void testProjectionPushdown() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_ROW_TYPE)); - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_projection_pushdown_", "(id BIGINT, root ROW(f1 BIGINT, f2 BIGINT))", ImmutableList.of("(1, ROW(1, 2))", "(2, NULl)", "(3, ROW(NULL, 4))"))) { @@ -7059,8 +7054,7 @@ public void testProjectionWithCaseSensitiveField() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_DEREFERENCE_PUSHDOWN)); - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_projection_with_case_sensitive_field_", "(id BIGINT, a ROW(\"UPPER_CASE\" BIGINT, \"lower_case\" BIGINT, \"MiXeD_cAsE\" BIGINT))", ImmutableList.of("(1, ROW(2, 3, 4))", "(5, ROW(6, 7, 8))"))) { @@ -7082,8 +7076,7 @@ public void testProjectionPushdownMultipleRows() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_DEREFERENCE_PUSHDOWN)); - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_projection_pushdown_multiple_rows_", "(id BIGINT, nested1 ROW(child1 BIGINT, child2 VARCHAR, child3 BIGINT), nested2 ROW(child1 DOUBLE, child2 BOOLEAN, child3 DATE))", ImmutableList.of( @@ -7131,8 +7124,7 @@ public void testProjectionPushdownWithHighlyNestedData() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_DEREFERENCE_PUSHDOWN)); - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_highly_nested_data_", "(id INT, row1_t ROW(f1 INT, f2 INT, row2_t ROW (f1 INT, f2 INT, row3_t ROW(f1 INT, f2 INT))))", ImmutableList.of("(1, ROW(2, 3, ROW(4, 5, ROW(6, 7))))", @@ -7164,8 +7156,7 @@ public void testProjectionPushdownReadsLessData() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_DEREFERENCE_PUSHDOWN)); - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_projection_pushdown_reads_less_data_", "AS SELECT val AS id, CAST(ROW(val + 1, val + 2) AS ROW(leaf1 BIGINT, leaf2 BIGINT)) AS root FROM UNNEST(SEQUENCE(1, 10)) AS t(val)")) { MaterializedResult expectedResult = computeActual("SELECT val + 2 FROM UNNEST(SEQUENCE(1, 10)) AS t(val)"); @@ -7203,8 +7194,7 @@ public void testProjectionPushdownPhysicalInputSize() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_DEREFERENCE_PUSHDOWN)); - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, + try (TestTable testTable = newTrinoTable( "test_projection_pushdown_physical_input_size_", "AS SELECT val AS id, CAST(ROW(val + 1, val + 2) AS ROW(leaf1 BIGINT, leaf2 BIGINT)) AS root FROM UNNEST(SEQUENCE(1, 10)) AS t(val)")) { // Verify that the physical input size is smaller when reading the root.leaf1 field compared to reading the root field diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index 11ab4ad04bec..1a218a5be589 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 468-SNAPSHOT + 469-SNAPSHOT ../../pom.xml 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 deleted file mode 100644 index df05f48d7907..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestConnectorEventListener.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.execution; - -import com.google.common.collect.ImmutableList; -import com.google.common.io.Closer; -import io.trino.connector.MockConnectorFactory; -import io.trino.spi.Plugin; -import io.trino.spi.connector.ConnectorFactory; -import io.trino.testing.DistributedQueryRunner; -import io.trino.testing.QueryRunner; -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.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(); - - private Closer closer; - private EventsAwaitingQueries queries; - - @BeforeAll - public void setUp() - throws Exception - { - closer = Closer.create(); - QueryRunner queryRunner = DistributedQueryRunner.builder(TEST_SESSION) - .setWorkerCount(0) - .build(); - closer.register(queryRunner); - - queryRunner.installPlugin(new Plugin() - { - @Override - public Iterable getConnectorFactories() - { - return ImmutableList.of(MockConnectorFactory.builder() - .withEventListener(new TestingEventListener(generatedEvents)) - .build()); - } - }); - queryRunner.createCatalog("mock-catalog", "mock"); - - queryRunner.getCoordinator().addConnectorEventListeners(); - queries = new EventsAwaitingQueries(generatedEvents, queryRunner); - } - - @AfterAll - public void tearDown() - throws IOException - { - if (closer != null) { - closer.close(); - } - closer = null; - } - - @Test - public void testConnectorEventHandlerReceivingEvents() - throws Exception - { - queries.runQueryAndWaitForEvents("SELECT 1", TEST_SESSION).getQueryEvents(); - } -} 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 f6af3da9e751..60d923fb3b34 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 @@ -95,6 +95,7 @@ private static BasicQueryInfo createQueryInfo(String queryId, QueryState state) DataSize.valueOf("23GB"), DataSize.valueOf("23GB"), DataSize.valueOf("23GB"), + DataSize.valueOf("23GB"), 24, 25, DataSize.valueOf("26GB"), @@ -105,6 +106,10 @@ private static BasicQueryInfo createQueryInfo(String queryId, QueryState state) new Duration(31, MINUTES), new Duration(32, MINUTES), new Duration(33, MINUTES), + new Duration(34, MINUTES), + new Duration(35, MINUTES), + new Duration(36, MINUTES), + new Duration(37, MINUTES), true, ImmutableSet.of(WAITING_FOR_MEMORY), OptionalDouble.of(20), diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/IcebergCostBasedPlanTestSetup.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/IcebergCostBasedPlanTestSetup.java index 574f02faa61b..3d446863f4f0 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/IcebergCostBasedPlanTestSetup.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/IcebergCostBasedPlanTestSetup.java @@ -19,8 +19,8 @@ import io.airlift.log.Logger; import io.trino.metastore.Database; import io.trino.metastore.HiveMetastore; +import io.trino.metastore.HiveMetastoreFactory; import io.trino.metastore.Table; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.iceberg.IcebergConnector; import io.trino.plugin.iceberg.IcebergConnectorFactory; import io.trino.spi.connector.Connector;