diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9115f719e2de..a20bf398fc5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,7 @@ jobs: if: steps.cache.outputs.cache-hit != 'true' run: rm -rf ~/.m2/repository/io/trino/trino-* - check-commits: + check-commits-dispatcher: runs-on: ubuntu-latest if: github.event_name == 'pull_request' outputs: @@ -101,12 +101,12 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 # checkout all commits to be able to determine merge base - - name: Check Commits + - name: Block illegal commits uses: trinodb/github-actions/block-commits@c2991972560c5219d9ae5fb68c0c9d687ffcdd10 with: action-merge: fail action-fixup: none - - name: Set matrix + - name: Set matrix (dispatch commit checks) id: set-matrix run: | # The output from rev-list ends with a newline, so we have to filter out index -1 in jq since it's an empty string @@ -125,12 +125,13 @@ jobs: echo "Commit matrix: $(jq '.' commit-matrix.json)" echo "matrix=$(jq -c '.' commit-matrix.json)" >> $GITHUB_OUTPUT - check-commits-dispatcher: + check-commit: runs-on: ubuntu-latest - needs: check-commits - if: github.event_name == 'pull_request' && needs.check-commits.outputs.matrix != '' + needs: check-commits-dispatcher + if: github.event_name == 'pull_request' && needs.check-commits-dispatcher.outputs.matrix != '' strategy: - matrix: ${{ fromJson(needs.check-commits.outputs.matrix) }} + fail-fast: false + matrix: ${{ fromJson(needs.check-commits-dispatcher.outputs.matrix) }} steps: - uses: actions/checkout@v3 with: diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index 10d868304314..5146a7f34e92 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/client/trino-client/pom.xml b/client/trino-client/pom.xml index b14dcc92265a..07ab050377c6 100644 --- a/client/trino-client/pom.xml +++ b/client/trino-client/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index 08647ebe6bc7..893d5cc0049b 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java index 5677816e5eaa..509e939a0481 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java @@ -813,7 +813,7 @@ public void testGetColumns() assertColumnSpec(rs, Types.TIME, 15L, null, 6L, null, createTimeType(6)); assertColumnSpec(rs, Types.TIME, 18L, null, 9L, null, createTimeType(9)); assertColumnSpec(rs, Types.TIME, 21L, null, 12L, null, createTimeType(12)); - assertColumnSpec(rs, Types.TIME_WITH_TIMEZONE, 18L, null, 3L, null, TimeWithTimeZoneType.TIME_WITH_TIME_ZONE); + assertColumnSpec(rs, Types.TIME_WITH_TIMEZONE, 18L, null, 3L, null, TimeWithTimeZoneType.TIME_TZ_MILLIS); assertColumnSpec(rs, Types.TIME_WITH_TIMEZONE, 14L, null, 0L, null, createTimeWithTimeZoneType(0)); assertColumnSpec(rs, Types.TIME_WITH_TIMEZONE, 18L, null, 3L, null, createTimeWithTimeZoneType(3)); assertColumnSpec(rs, Types.TIME_WITH_TIMEZONE, 21L, null, 6L, null, createTimeWithTimeZoneType(6)); diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index 03619df45be0..073fb15da5f5 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java b/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java index 9b3a2f90a6bd..49c7cfeb1825 100644 --- a/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java +++ b/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java @@ -60,7 +60,8 @@ public final class SystemSessionProperties public static final String JOIN_MAX_BROADCAST_TABLE_SIZE = "join_max_broadcast_table_size"; public static final String JOIN_MULTI_CLAUSE_INDEPENDENCE_FACTOR = "join_multi_clause_independence_factor"; public static final String DISTRIBUTED_INDEX_JOIN = "distributed_index_join"; - public static final String HASH_PARTITION_COUNT = "hash_partition_count"; + public static final String MAX_HASH_PARTITION_COUNT = "max_hash_partition_count"; + public static final String MIN_HASH_PARTITION_COUNT = "min_hash_partition_count"; public static final String PREFER_STREAMING_OPERATORS = "prefer_streaming_operators"; public static final String TASK_WRITER_COUNT = "task_writer_count"; public static final String TASK_PARTITIONED_WRITER_COUNT = "task_partitioned_writer_count"; @@ -80,6 +81,7 @@ public final class SystemSessionProperties public static final String PREFERRED_WRITE_PARTITIONING_MIN_NUMBER_OF_PARTITIONS = "preferred_write_partitioning_min_number_of_partitions"; public static final String SCALE_WRITERS = "scale_writers"; public static final String TASK_SCALE_WRITERS_ENABLED = "task_scale_writers_enabled"; + public static final String MAX_WRITERS_NODES_COUNT = "max_writers_nodes_count"; public static final String TASK_SCALE_WRITERS_MAX_WRITER_COUNT = "task_scale_writers_max_writer_count"; public static final String WRITER_MIN_SIZE = "writer_min_size"; public static final String PUSH_TABLE_WRITE_THROUGH_UNION = "push_table_write_through_union"; @@ -173,10 +175,13 @@ public final class SystemSessionProperties public static final String ADAPTIVE_PARTIAL_AGGREGATION_MIN_ROWS = "adaptive_partial_aggregation_min_rows"; public static final String ADAPTIVE_PARTIAL_AGGREGATION_UNIQUE_ROWS_RATIO_THRESHOLD = "adaptive_partial_aggregation_unique_rows_ratio_threshold"; public static final String JOIN_PARTITIONED_BUILD_MIN_ROW_COUNT = "join_partitioned_build_min_row_count"; + public static final String MIN_INPUT_SIZE_PER_TASK = "min_input_size_per_task"; + public static final String MIN_INPUT_ROWS_PER_TASK = "min_input_rows_per_task"; public static final String USE_EXACT_PARTITIONING = "use_exact_partitioning"; public static final String FORCE_SPILLING_JOIN = "force_spilling_join"; public static final String FAULT_TOLERANT_EXECUTION_EVENT_DRIVEN_SCHEDULER_ENABLED = "fault_tolerant_execution_event_driven_scheduler_enabled"; public static final String FAULT_TOLERANT_EXECUTION_FORCE_PREFERRED_WRITE_PARTITIONING_ENABLED = "fault_tolerant_execution_force_preferred_write_partitioning_enabled"; + public static final String PAGE_PARTITIONING_BUFFER_POOL_SIZE = "page_partitioning_buffer_pool_size"; private final List> sessionProperties; @@ -241,9 +246,14 @@ public SystemSessionProperties( optimizerConfig.isDistributedIndexJoinsEnabled(), false), integerProperty( - HASH_PARTITION_COUNT, - "Number of partitions for distributed joins and aggregations", - queryManagerConfig.getHashPartitionCount(), + MAX_HASH_PARTITION_COUNT, + "Maximum number of partitions for distributed joins and aggregations", + queryManagerConfig.getMaxHashPartitionCount(), + false), + integerProperty( + MIN_HASH_PARTITION_COUNT, + "Minimum number of partitions for distributed joins and aggregations", + queryManagerConfig.getMinHashPartitionCount(), false), booleanProperty( PREFER_STREAMING_OPERATORS, @@ -286,6 +296,11 @@ public SystemSessionProperties( "Scale out writers based on throughput (use minimum necessary)", featuresConfig.isScaleWriters(), false), + integerProperty( + MAX_WRITERS_NODES_COUNT, + "Set upper limit on number of nodes that take part in writing if task.scale-writers.enabled is set", + queryManagerConfig.getMaxWritersNodesCount(), + false), booleanProperty( TASK_SCALE_WRITERS_ENABLED, "Scale the number of concurrent table writers per task based on throughput", @@ -860,6 +875,16 @@ public SystemSessionProperties( optimizerConfig.getJoinPartitionedBuildMinRowCount(), value -> validateNonNegativeLongValue(value, JOIN_PARTITIONED_BUILD_MIN_ROW_COUNT), false), + dataSizeProperty( + MIN_INPUT_SIZE_PER_TASK, + "Minimum input data size required per task. This will help optimizer determine hash partition count for joins and aggregations", + optimizerConfig.getMinInputSizePerTask(), + false), + longProperty( + MIN_INPUT_ROWS_PER_TASK, + "Minimum input rows required per task. This will help optimizer determine hash partition count for joins and aggregations", + optimizerConfig.getMinInputRowsPerTask(), + false), booleanProperty( USE_EXACT_PARTITIONING, "When enabled this forces data repartitioning unless the partitioning of upstream stage matches exactly what downstream stage expects", @@ -879,6 +904,10 @@ public SystemSessionProperties( FAULT_TOLERANT_EXECUTION_FORCE_PREFERRED_WRITE_PARTITIONING_ENABLED, "Force preferred write partitioning for fault tolerant execution", queryManagerConfig.isFaultTolerantExecutionForcePreferredWritePartitioningEnabled(), + true), + integerProperty(PAGE_PARTITIONING_BUFFER_POOL_SIZE, + "Maximum number of free buffers in the per task partitioned page buffer pool. Setting this to zero effectively disables the pool", + taskManagerConfig.getPagePartitioningBufferPoolSize(), true)); } @@ -918,9 +947,14 @@ public static boolean isDistributedIndexJoinEnabled(Session session) return session.getSystemProperty(DISTRIBUTED_INDEX_JOIN, Boolean.class); } - public static int getHashPartitionCount(Session session) + public static int getMaxHashPartitionCount(Session session) + { + return session.getSystemProperty(MAX_HASH_PARTITION_COUNT, Integer.class); + } + + public static int getMinHashPartitionCount(Session session) { - return session.getSystemProperty(HASH_PARTITION_COUNT, Integer.class); + return session.getSystemProperty(MIN_HASH_PARTITION_COUNT, Integer.class); } public static boolean preferStreamingOperators(Session session) @@ -968,6 +1002,11 @@ public static int getTaskScaleWritersMaxWriterCount(Session session) return session.getSystemProperty(TASK_SCALE_WRITERS_MAX_WRITER_COUNT, Integer.class); } + public static int getMaxWritersNodesCount(Session session) + { + return session.getSystemProperty(MAX_WRITERS_NODES_COUNT, Integer.class); + } + public static DataSize getWriterMinSize(Session session) { return session.getSystemProperty(WRITER_MIN_SIZE, DataSize.class); @@ -1548,6 +1587,16 @@ public static long getJoinPartitionedBuildMinRowCount(Session session) return session.getSystemProperty(JOIN_PARTITIONED_BUILD_MIN_ROW_COUNT, Long.class); } + public static DataSize getMinInputSizePerTask(Session session) + { + return session.getSystemProperty(MIN_INPUT_SIZE_PER_TASK, DataSize.class); + } + + public static long getMinInputRowsPerTask(Session session) + { + return session.getSystemProperty(MIN_INPUT_ROWS_PER_TASK, Long.class); + } + public static boolean isUseExactPartitioning(Session session) { return session.getSystemProperty(USE_EXACT_PARTITIONING, Boolean.class); @@ -1571,4 +1620,9 @@ public static boolean isFaultTolerantExecutionForcePreferredWritePartitioningEna } return session.getSystemProperty(FAULT_TOLERANT_EXECUTION_FORCE_PREFERRED_WRITE_PARTITIONING_ENABLED, Boolean.class); } + + public static int getPagePartitioningBufferPoolSize(Session session) + { + return session.getSystemProperty(PAGE_PARTITIONING_BUFFER_POOL_SIZE, Integer.class); + } } diff --git a/core/trino-main/src/main/java/io/trino/connector/ConnectorContextInstance.java b/core/trino-main/src/main/java/io/trino/connector/ConnectorContextInstance.java index cf7549f1c235..486cd3b23d2e 100644 --- a/core/trino-main/src/main/java/io/trino/connector/ConnectorContextInstance.java +++ b/core/trino-main/src/main/java/io/trino/connector/ConnectorContextInstance.java @@ -17,6 +17,7 @@ import io.trino.spi.PageIndexerFactory; import io.trino.spi.PageSorter; import io.trino.spi.VersionEmbedder; +import io.trino.spi.connector.CatalogHandle; import io.trino.spi.connector.ConnectorContext; import io.trino.spi.connector.MetadataProvider; import io.trino.spi.type.TypeManager; @@ -38,8 +39,10 @@ public class ConnectorContextInstance private final PageIndexerFactory pageIndexerFactory; private final Supplier duplicatePluginClassLoaderFactory; private final AtomicBoolean pluginClassLoaderDuplicated = new AtomicBoolean(); + private final CatalogHandle catalogHandle; public ConnectorContextInstance( + CatalogHandle catalogHandle, NodeManager nodeManager, VersionEmbedder versionEmbedder, TypeManager typeManager, @@ -55,6 +58,13 @@ public ConnectorContextInstance( this.pageSorter = requireNonNull(pageSorter, "pageSorter is null"); this.pageIndexerFactory = requireNonNull(pageIndexerFactory, "pageIndexerFactory is null"); this.duplicatePluginClassLoaderFactory = requireNonNull(duplicatePluginClassLoaderFactory, "duplicatePluginClassLoaderFactory is null"); + this.catalogHandle = requireNonNull(catalogHandle, "catalogHandle is null"); + } + + @Override + public CatalogHandle getCatalogHandle() + { + return catalogHandle; } @Override diff --git a/core/trino-main/src/main/java/io/trino/connector/DefaultCatalogFactory.java b/core/trino-main/src/main/java/io/trino/connector/DefaultCatalogFactory.java index f16872bc4e9d..712ec147f958 100644 --- a/core/trino-main/src/main/java/io/trino/connector/DefaultCatalogFactory.java +++ b/core/trino-main/src/main/java/io/trino/connector/DefaultCatalogFactory.java @@ -190,6 +190,7 @@ private Connector createConnector( Map properties) { ConnectorContext context = new ConnectorContextInstance( + catalogHandle, new ConnectorAwareNodeManager(nodeManager, nodeInfo.getEnvironment(), catalogHandle, schedulerIncludeCoordinator), versionEmbedder, typeManager, diff --git a/core/trino-main/src/main/java/io/trino/connector/InternalMetadataProvider.java b/core/trino-main/src/main/java/io/trino/connector/InternalMetadataProvider.java index 2f76f9778429..5eeb0447bf74 100644 --- a/core/trino-main/src/main/java/io/trino/connector/InternalMetadataProvider.java +++ b/core/trino-main/src/main/java/io/trino/connector/InternalMetadataProvider.java @@ -13,6 +13,7 @@ */ package io.trino.connector; +import com.google.common.collect.ImmutableList; import io.trino.FullConnectorSession; import io.trino.Session; import io.trino.metadata.MaterializedViewDefinition; @@ -54,12 +55,12 @@ public Optional getRelationMetadata(ConnectorSession conne Optional materializedView = metadata.getMaterializedView(session, qualifiedName); if (materializedView.isPresent()) { - return Optional.of(new ConnectorTableSchema(tableName.getSchemaTableName(), toColumnSchema(materializedView.get().getColumns()))); + return Optional.of(new ConnectorTableSchema(tableName.getSchemaTableName(), toColumnSchema(materializedView.get().getColumns()), ImmutableList.of())); } Optional view = metadata.getView(session, qualifiedName); if (view.isPresent()) { - return Optional.of(new ConnectorTableSchema(tableName.getSchemaTableName(), toColumnSchema(view.get().getColumns()))); + return Optional.of(new ConnectorTableSchema(tableName.getSchemaTableName(), toColumnSchema(view.get().getColumns()), ImmutableList.of())); } Optional tableHandle = metadata.getTableHandle(session, qualifiedName); 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 29998ce48fad..c1c5ca958a99 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 @@ -54,6 +54,7 @@ import static io.trino.connector.informationschema.InformationSchemaMetadata.defaultPrefixes; import static io.trino.connector.informationschema.InformationSchemaMetadata.isTablesEnumeratingTable; import static io.trino.metadata.MetadataListing.getViews; +import static io.trino.metadata.MetadataListing.listMaterializedViews; import static io.trino.metadata.MetadataListing.listSchemas; import static io.trino.metadata.MetadataListing.listTableColumns; import static io.trino.metadata.MetadataListing.listTablePrivileges; @@ -271,12 +272,18 @@ private void addColumnsRecords(QualifiedTablePrefix prefix) private void addTablesRecords(QualifiedTablePrefix prefix) { Set tables = listTables(session, metadata, accessControl, prefix); + Set materializedViews = listMaterializedViews(session, metadata, accessControl, prefix); Set views = listViews(session, metadata, accessControl, prefix); - // TODO (https://github.com/trinodb/trino/issues/8207) define a type for materialized views - for (SchemaTableName name : union(tables, views)) { + for (SchemaTableName name : union(union(tables, materializedViews), views)) { // if table and view names overlap, the view wins - String type = views.contains(name) ? "VIEW" : "BASE TABLE"; + String type = "BASE TABLE"; + if (materializedViews.contains(name)) { + type = "MATERIALIZED VIEW"; + } + else if (views.contains(name)) { + type = "VIEW"; + } addRecord( prefix.getCatalogName(), name.getSchemaName(), diff --git a/core/trino-main/src/main/java/io/trino/cost/TaskCountEstimator.java b/core/trino-main/src/main/java/io/trino/cost/TaskCountEstimator.java index 3eaa86ecac2f..488bb5749281 100644 --- a/core/trino-main/src/main/java/io/trino/cost/TaskCountEstimator.java +++ b/core/trino-main/src/main/java/io/trino/cost/TaskCountEstimator.java @@ -26,7 +26,7 @@ import static io.trino.SystemSessionProperties.getCostEstimationWorkerCount; import static io.trino.SystemSessionProperties.getFaultTolerantExecutionPartitionCount; -import static io.trino.SystemSessionProperties.getHashPartitionCount; +import static io.trino.SystemSessionProperties.getMaxHashPartitionCount; import static io.trino.SystemSessionProperties.getRetryPolicy; import static java.lang.Math.min; import static java.lang.Math.toIntExact; @@ -73,7 +73,7 @@ public int estimateHashedTaskCount(Session session) partitionCount = getFaultTolerantExecutionPartitionCount(session); } else { - partitionCount = getHashPartitionCount(session); + partitionCount = getMaxHashPartitionCount(session); } return min(estimateSourceDistributedTaskCount(session), partitionCount); } diff --git a/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java b/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java index e391706a4c45..5ffe096f1b96 100644 --- a/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java +++ b/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java @@ -53,7 +53,10 @@ public class QueryManagerConfig private int maxConcurrentQueries = 1000; private int maxQueuedQueries = 5000; - private int hashPartitionCount = 100; + private int maxHashPartitionCount = 100; + private int minHashPartitionCount = 4; + private int maxWritersNodesCount = 100; + private Duration minQueryExpireAge = new Duration(15, TimeUnit.MINUTES); private int maxQueryHistory = 100; private int maxQueryLength = 1_000_000; @@ -159,17 +162,46 @@ public QueryManagerConfig setMaxQueuedQueries(int maxQueuedQueries) } @Min(1) - public int getHashPartitionCount() + public int getMaxHashPartitionCount() + { + return maxHashPartitionCount; + } + + @Config("query.max-hash-partition-count") + @LegacyConfig({"query.initial-hash-partitions", "query.hash-partition-count"}) + @ConfigDescription("Maximum number of partitions for distributed joins and aggregations") + public QueryManagerConfig setMaxHashPartitionCount(int maxHashPartitionCount) + { + this.maxHashPartitionCount = maxHashPartitionCount; + return this; + } + + @Min(1) + public int getMinHashPartitionCount() + { + return minHashPartitionCount; + } + + @Config("query.min-hash-partition-count") + @ConfigDescription("Minimum number of partitions for distributed joins and aggregations") + public QueryManagerConfig setMinHashPartitionCount(int minHashPartitionCount) + { + this.minHashPartitionCount = minHashPartitionCount; + return this; + } + + @Min(1) + public int getMaxWritersNodesCount() { - return hashPartitionCount; + return maxWritersNodesCount; } - @Config("query.hash-partition-count") - @LegacyConfig("query.initial-hash-partitions") - @ConfigDescription("Number of partitions for distributed joins and aggregations") - public QueryManagerConfig setHashPartitionCount(int hashPartitionCount) + @Config("query.max-writer-node-count") + @ConfigDescription("Maximum number of nodes that will take part in writer tasks. It is an upper bound on scaling of writers " + + "and works only if task.scale-writers.enabled is set") + public QueryManagerConfig setMaxWritersNodesCount(int maxWritersNodesCount) { - this.hashPartitionCount = hashPartitionCount; + this.maxWritersNodesCount = maxWritersNodesCount; return this; } diff --git a/core/trino-main/src/main/java/io/trino/execution/SqlTask.java b/core/trino-main/src/main/java/io/trino/execution/SqlTask.java index afcbc76d6997..975c6d310d8f 100644 --- a/core/trino-main/src/main/java/io/trino/execution/SqlTask.java +++ b/core/trino-main/src/main/java/io/trino/execution/SqlTask.java @@ -278,6 +278,7 @@ private TaskStatus createTaskStatus(TaskHolder taskHolder) long runningPartitionedSplitsWeight = 0L; DataSize outputDataSize = DataSize.ofBytes(0); DataSize physicalWrittenDataSize = DataSize.ofBytes(0); + Optional writerCount = Optional.empty(); DataSize userMemoryReservation = DataSize.ofBytes(0); DataSize peakUserMemoryReservation = DataSize.ofBytes(0); DataSize revocableMemoryReservation = DataSize.ofBytes(0); @@ -292,6 +293,7 @@ private TaskStatus createTaskStatus(TaskHolder taskHolder) runningPartitionedDrivers = taskStats.getRunningPartitionedDrivers(); runningPartitionedSplitsWeight = taskStats.getRunningPartitionedSplitsWeight(); physicalWrittenDataSize = taskStats.getPhysicalWrittenDataSize(); + writerCount = taskStats.getMaxWriterCount(); userMemoryReservation = taskStats.getUserMemoryReservation(); peakUserMemoryReservation = taskStats.getPeakUserMemoryReservation(); revocableMemoryReservation = taskStats.getRevocableMemoryReservation(); @@ -312,6 +314,7 @@ else if (taskHolder.getTaskExecution() != null) { physicalWrittenBytes += pipelineContext.getPhysicalWrittenDataSize(); } physicalWrittenDataSize = succinctBytes(physicalWrittenBytes); + writerCount = taskContext.getMaxWriterCount(); userMemoryReservation = taskContext.getMemoryReservation(); peakUserMemoryReservation = taskContext.getPeakMemoryReservation(); revocableMemoryReservation = taskContext.getRevocableMemoryReservation(); @@ -334,6 +337,7 @@ else if (taskHolder.getTaskExecution() != null) { outputBuffer.getStatus(), outputDataSize, physicalWrittenDataSize, + writerCount, userMemoryReservation, peakUserMemoryReservation, revocableMemoryReservation, diff --git a/core/trino-main/src/main/java/io/trino/execution/TaskManagerConfig.java b/core/trino-main/src/main/java/io/trino/execution/TaskManagerConfig.java index 846e17136931..dc27dea56ca3 100644 --- a/core/trino-main/src/main/java/io/trino/execution/TaskManagerConfig.java +++ b/core/trino-main/src/main/java/io/trino/execution/TaskManagerConfig.java @@ -64,6 +64,7 @@ public class TaskManagerConfig private DataSize sinkMaxBufferSize = DataSize.of(32, Unit.MEGABYTE); private DataSize sinkMaxBroadcastBufferSize = DataSize.of(200, Unit.MEGABYTE); private DataSize maxPagePartitioningBufferSize = DataSize.of(32, Unit.MEGABYTE); + private int pagePartitioningBufferPoolSize = 8; private Duration clientTimeout = new Duration(2, TimeUnit.MINUTES); private Duration infoMaxAge = new Duration(15, TimeUnit.MINUTES); @@ -77,10 +78,11 @@ public class TaskManagerConfig private Duration interruptStuckSplitTasksDetectionInterval = new Duration(2, TimeUnit.MINUTES); private boolean scaleWritersEnabled = true; - // The default value is 8 because it is better in performance compare to 2 or 4 - // and acceptable in terms of resource utilization since values like 32 or higher could take - // more resources, hence potentially affect the other concurrent queries in the cluster. - private int scaleWritersMaxWriterCount = 8; + // Set the value of default max writer count to the number of processors and cap it to 32. We can do this + // because preferred write partitioning is always enabled for local exchange thus partitioned inserts will never + // use this property. Hence, there is no risk in terms of more numbers of physical writers which can cause high + // resource utilization. + private int scaleWritersMaxWriterCount = min(getAvailablePhysicalProcessorCount(), 32); private int writerCount = 1; // Default value of partitioned task writer count should be above 1, otherwise it can create a plan // with a single gather exchange node on the coordinator due to a single available processor. Whereas, @@ -377,6 +379,20 @@ public TaskManagerConfig setMaxPagePartitioningBufferSize(DataSize size) return this; } + @Min(0) + public int getPagePartitioningBufferPoolSize() + { + return pagePartitioningBufferPoolSize; + } + + @Config("driver.page-partitioning-buffer-pool-size") + @ConfigDescription("Maximum number of free buffers in the per task partitioned page buffer pool. Setting this to zero effectively disables the pool") + public TaskManagerConfig setPagePartitioningBufferPoolSize(int pagePartitioningBufferPoolSize) + { + this.pagePartitioningBufferPoolSize = pagePartitioningBufferPoolSize; + return this; + } + @MinDuration("5s") @NotNull public Duration getClientTimeout() diff --git a/core/trino-main/src/main/java/io/trino/execution/TaskStatus.java b/core/trino-main/src/main/java/io/trino/execution/TaskStatus.java index 839154c85a39..50f1aac49b0d 100644 --- a/core/trino-main/src/main/java/io/trino/execution/TaskStatus.java +++ b/core/trino-main/src/main/java/io/trino/execution/TaskStatus.java @@ -22,6 +22,7 @@ import java.net.URI; import java.util.List; +import java.util.Optional; import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; @@ -59,6 +60,7 @@ public class TaskStatus private final OutputBufferStatus outputBufferStatus; private final DataSize outputDataSize; private final DataSize physicalWrittenDataSize; + private final Optional maxWriterCount; private final DataSize memoryReservation; private final DataSize peakMemoryReservation; private final DataSize revocableMemoryReservation; @@ -84,6 +86,7 @@ public TaskStatus( @JsonProperty("outputBufferStatus") OutputBufferStatus outputBufferStatus, @JsonProperty("outputDataSize") DataSize outputDataSize, @JsonProperty("physicalWrittenDataSize") DataSize physicalWrittenDataSize, + @JsonProperty("writerCount") Optional maxWriterCount, @JsonProperty("memoryReservation") DataSize memoryReservation, @JsonProperty("peakMemoryReservation") DataSize peakMemoryReservation, @JsonProperty("revocableMemoryReservation") DataSize revocableMemoryReservation, @@ -116,6 +119,7 @@ public TaskStatus( this.outputDataSize = requireNonNull(outputDataSize, "outputDataSize is null"); this.physicalWrittenDataSize = requireNonNull(physicalWrittenDataSize, "physicalWrittenDataSize is null"); + this.maxWriterCount = requireNonNull(maxWriterCount, "maxWriterCount is null"); this.memoryReservation = requireNonNull(memoryReservation, "memoryReservation is null"); this.peakMemoryReservation = requireNonNull(peakMemoryReservation, "peakMemoryReservation is null"); @@ -189,6 +193,12 @@ public DataSize getPhysicalWrittenDataSize() return physicalWrittenDataSize; } + @JsonProperty + public Optional getMaxWriterCount() + { + return maxWriterCount; + } + @JsonProperty public OutputBufferStatus getOutputBufferStatus() { @@ -273,6 +283,7 @@ public static TaskStatus initialTaskStatus(TaskId taskId, URI location, String n OutputBufferStatus.initial(), DataSize.ofBytes(0), DataSize.ofBytes(0), + Optional.empty(), DataSize.ofBytes(0), DataSize.ofBytes(0), DataSize.ofBytes(0), @@ -298,6 +309,7 @@ public static TaskStatus failWith(TaskStatus taskStatus, TaskState state, List partitioningCacheMap = new HashMap<>(); - Function partitioningCache = partitioningHandle -> + BiFunction, NodePartitionMap> partitioningCache = (partitioningHandle, partitionCount) -> partitioningCacheMap.computeIfAbsent(partitioningHandle, handle -> nodePartitioningManager.getNodePartitioningMap( queryStateMachine.getSession(), // TODO: support hash distributed writer scaling (https://github.com/trinodb/trino/issues/10791) - handle.equals(SCALED_WRITER_HASH_DISTRIBUTION) ? FIXED_HASH_DISTRIBUTION : handle)); + handle.equals(SCALED_WRITER_HASH_DISTRIBUTION) ? FIXED_HASH_DISTRIBUTION : handle, + partitionCount)); Map> bucketToPartitionMap = createBucketToPartitionMap( coordinatorStagesScheduler.getBucketToPartitionForStagesConsumedByCoordinator(), @@ -937,13 +936,18 @@ public static DistributedStagesScheduler create( private static Map> createBucketToPartitionMap( Map> bucketToPartitionForStagesConsumedByCoordinator, StageManager stageManager, - Function partitioningCache) + BiFunction, NodePartitionMap> partitioningCache) { ImmutableMap.Builder> result = ImmutableMap.builder(); result.putAll(bucketToPartitionForStagesConsumedByCoordinator); for (SqlStage stage : stageManager.getDistributedStagesInTopologicalOrder()) { PlanFragment fragment = stage.getFragment(); - Optional bucketToPartition = getBucketToPartition(fragment.getPartitioning(), partitioningCache, fragment.getRoot(), fragment.getRemoteSourceNodes()); + Optional bucketToPartition = getBucketToPartition( + fragment.getPartitioning(), + partitioningCache, + fragment.getRoot(), + fragment.getRemoteSourceNodes(), + fragment.getPartitioningScheme().getPartitionCount()); for (SqlStage childStage : stageManager.getChildren(stage.getStageId())) { result.put(childStage.getFragment().getId(), bucketToPartition); } @@ -953,9 +957,10 @@ private static Map> createBucketToPartitionMap( private static Optional getBucketToPartition( PartitioningHandle partitioningHandle, - Function partitioningCache, + BiFunction, NodePartitionMap> partitioningCache, PlanNode fragmentRoot, - List remoteSourceNodes) + List remoteSourceNodes, + Optional partitionCount) { if (partitioningHandle.equals(SOURCE_DISTRIBUTION) || partitioningHandle.equals(SCALED_WRITER_ROUND_ROBIN_DISTRIBUTION)) { return Optional.of(new int[1]); @@ -965,10 +970,10 @@ private static Optional getBucketToPartition( return Optional.empty(); } // remote source requires nodePartitionMap - NodePartitionMap nodePartitionMap = partitioningCache.apply(partitioningHandle); + NodePartitionMap nodePartitionMap = partitioningCache.apply(partitioningHandle, partitionCount); return Optional.of(nodePartitionMap.getBucketToPartition()); } - NodePartitionMap nodePartitionMap = partitioningCache.apply(partitioningHandle); + NodePartitionMap nodePartitionMap = partitioningCache.apply(partitioningHandle, partitionCount); List partitionToNode = nodePartitionMap.getPartitionToNode(); // todo this should asynchronously wait a standard timeout period before failing checkCondition(!partitionToNode.isEmpty(), NO_NODES_AVAILABLE, "No worker nodes available"); @@ -1011,7 +1016,7 @@ private static StageScheduler createStageScheduler( StageExecution stageExecution, SplitSourceFactory splitSourceFactory, List childStageExecutions, - Function partitioningCache, + BiFunction, NodePartitionMap> partitioningCache, NodeScheduler nodeScheduler, NodePartitioningManager nodePartitioningManager, int splitBatchSize, @@ -1022,6 +1027,7 @@ private static StageScheduler createStageScheduler( Session session = queryStateMachine.getSession(); PlanFragment fragment = stageExecution.getFragment(); PartitioningHandle partitioningHandle = fragment.getPartitioning(); + Optional partitionCount = fragment.getPartitioningScheme().getPartitionCount(); Map splitSources = splitSourceFactory.createSplitSources(session, fragment); if (!splitSources.isEmpty()) { queryStateMachine.addStateChangeListener(new StateChangeListener<>() @@ -1077,7 +1083,7 @@ public void stateChanged(QueryState newState) nodeScheduler.createNodeSelector(session, Optional.empty()), executor, getWriterMinSize(session), - isTaskScaleWritersEnabled(session) ? getTaskScaleWritersMaxWriterCount(session) : getTaskWriterCount(session)); + getMaxWritersNodesCount(session)); whenAllStages(childStageExecutions, StageExecution.State::isDone) .addListener(scheduler::finish, directExecutor()); @@ -1087,7 +1093,7 @@ public void stateChanged(QueryState newState) if (splitSources.isEmpty()) { // all sources are remote - NodePartitionMap nodePartitionMap = partitioningCache.apply(partitioningHandle); + NodePartitionMap nodePartitionMap = partitioningCache.apply(partitioningHandle, partitionCount); List partitionToNode = nodePartitionMap.getPartitionToNode(); // todo this should asynchronously wait a standard timeout period before failing checkCondition(!partitionToNode.isEmpty(), NO_NODES_AVAILABLE, "No worker nodes available"); @@ -1109,7 +1115,7 @@ public void stateChanged(QueryState newState) } else { // remote source requires nodePartitionMap - NodePartitionMap nodePartitionMap = partitioningCache.apply(partitioningHandle); + NodePartitionMap nodePartitionMap = partitioningCache.apply(partitioningHandle, partitionCount); stageNodeList = nodePartitionMap.getPartitionToNode(); bucketNodeMap = nodePartitionMap.asBucketNodeMap(); } diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/ScaledWriterScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/ScaledWriterScheduler.java index 828cdba1bf79..39dd15a8a281 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/ScaledWriterScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/ScaledWriterScheduler.java @@ -48,9 +48,9 @@ public class ScaledWriterScheduler private final NodeSelector nodeSelector; private final ScheduledExecutorService executor; private final long writerMinSizeBytes; - private final int maxTaskWriterCount; private final Set scheduledNodes = new HashSet<>(); private final AtomicBoolean done = new AtomicBoolean(); + private final int maxWritersNodesCount; private volatile SettableFuture future = SettableFuture.create(); public ScaledWriterScheduler( @@ -60,7 +60,7 @@ public ScaledWriterScheduler( NodeSelector nodeSelector, ScheduledExecutorService executor, DataSize writerMinSize, - int maxTaskWriterCount) + int maxWritersNodesCount) { this.stage = requireNonNull(stage, "stage is null"); this.sourceTasksProvider = requireNonNull(sourceTasksProvider, "sourceTasksProvider is null"); @@ -68,7 +68,7 @@ public ScaledWriterScheduler( this.nodeSelector = requireNonNull(nodeSelector, "nodeSelector is null"); this.executor = requireNonNull(executor, "executor is null"); this.writerMinSizeBytes = writerMinSize.toBytes(); - this.maxTaskWriterCount = maxTaskWriterCount; + this.maxWritersNodesCount = maxWritersNodesCount; } public void finish() @@ -95,21 +95,44 @@ private int getNewTaskCount() return 1; } - long writtenBytes = writerTasksProvider.get().stream() - .map(TaskStatus::getPhysicalWrittenDataSize) - .mapToLong(DataSize::toBytes) - .sum(); + Collection writerTasks = writerTasksProvider.get(); + // Do not scale tasks until all existing writer tasks are initialized with maxWriterCount + if (writerTasks.size() != scheduledNodes.size() + || writerTasks.stream().map(TaskStatus::getMaxWriterCount).anyMatch(Optional::isEmpty)) { + return 0; + } // When there is a big data skewness, there could be a bottleneck due to the skewed workers even if most of the workers are not over-utilized. // Check both, weighted output buffer over-utilization rate and average output buffer over-utilization rate, in case when there are many over-utilized small tasks // due to fewer not-over-utilized big skewed tasks. - if ((isWeightedBufferFull() || isAverageBufferFull()) && (writtenBytes >= (writerMinSizeBytes * maxTaskWriterCount * scheduledNodes.size()))) { + if (isSourceTasksBufferFull() && isWriteThroughputSufficient() && scheduledNodes.size() < maxWritersNodesCount) { return 1; } return 0; } + private boolean isSourceTasksBufferFull() + { + return isAverageBufferFull() || isWeightedBufferFull(); + } + + private boolean isWriteThroughputSufficient() + { + Collection writerTasks = writerTasksProvider.get(); + long writtenBytes = writerTasks.stream() + .map(TaskStatus::getPhysicalWrittenDataSize) + .mapToLong(DataSize::toBytes) + .sum(); + + long minWrittenBytesToScaleUp = writerTasks.stream() + .map(TaskStatus::getMaxWriterCount) + .map(Optional::get) + .mapToLong(writerCount -> writerMinSizeBytes * writerCount) + .sum(); + return writtenBytes >= minWrittenBytesToScaleUp; + } + private boolean isWeightedBufferFull() { double totalOutputSize = 0.0; diff --git a/core/trino-main/src/main/java/io/trino/metadata/DisabledSystemSecurityMetadata.java b/core/trino-main/src/main/java/io/trino/metadata/DisabledSystemSecurityMetadata.java index efedd3b9e545..26c5ca2dc8a3 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/DisabledSystemSecurityMetadata.java +++ b/core/trino-main/src/main/java/io/trino/metadata/DisabledSystemSecurityMetadata.java @@ -184,6 +184,15 @@ public void tableRenamed(Session session, CatalogSchemaTableName sourceTable, Ca @Override public void tableDropped(Session session, CatalogSchemaTableName table) {} + @Override + public void columnCreated(Session session, CatalogSchemaTableName table, String column) {} + + @Override + public void columnRenamed(Session session, CatalogSchemaTableName table, String oldColumn, String newColumn) {} + + @Override + public void columnDropped(Session session, CatalogSchemaTableName table, String column) {} + private static TrinoException notSupportedException(String catalogName) { return new TrinoException(NOT_SUPPORTED, "Catalog does not support permission management: " + catalogName); diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index 5d3396433e95..42efa512ece5 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -726,6 +726,14 @@ public void renameColumn(Session session, TableHandle tableHandle, ColumnHandle CatalogHandle catalogHandle = tableHandle.getCatalogHandle(); ConnectorMetadata metadata = getMetadataForWrite(session, catalogHandle); metadata.renameColumn(session.toConnectorSession(catalogHandle), tableHandle.getConnectorHandle(), source, target.toLowerCase(ENGLISH)); + + CatalogMetadata catalogMetadata = getCatalogMetadataForWrite(session, catalogHandle); + ColumnMetadata sourceColumnMetadata = getColumnMetadata(session, tableHandle, source); + if (catalogMetadata.getSecurityManagement() != CONNECTOR) { + TableMetadata tableMetadata = getTableMetadata(session, tableHandle); + CatalogSchemaTableName sourceTableName = new CatalogSchemaTableName(catalogHandle.getCatalogName(), tableMetadata.getTable()); + systemSecurityMetadata.columnRenamed(session, sourceTableName, sourceColumnMetadata.getName(), target); + } } @Override @@ -734,6 +742,13 @@ public void addColumn(Session session, TableHandle tableHandle, ColumnMetadata c CatalogHandle catalogHandle = tableHandle.getCatalogHandle(); ConnectorMetadata metadata = getMetadataForWrite(session, catalogHandle); metadata.addColumn(session.toConnectorSession(catalogHandle), tableHandle.getConnectorHandle(), column); + + CatalogMetadata catalogMetadata = getCatalogMetadataForWrite(session, catalogHandle); + if (catalogMetadata.getSecurityManagement() != CONNECTOR) { + TableMetadata tableMetadata = getTableMetadata(session, tableHandle); + CatalogSchemaTableName sourceTableName = new CatalogSchemaTableName(catalogHandle.getCatalogName(), tableMetadata.getTable()); + systemSecurityMetadata.columnCreated(session, sourceTableName, column.getName()); + } } @Override @@ -741,7 +756,14 @@ public void dropColumn(Session session, TableHandle tableHandle, ColumnHandle co { CatalogHandle catalogHandle = tableHandle.getCatalogHandle(); ConnectorMetadata metadata = getMetadataForWrite(session, catalogHandle); + CatalogMetadata catalogMetadata = getCatalogMetadataForWrite(session, catalogHandle); metadata.dropColumn(session.toConnectorSession(catalogHandle), tableHandle.getConnectorHandle(), column); + if (catalogMetadata.getSecurityManagement() != CONNECTOR) { + String columnName = getColumnMetadata(session, tableHandle, column).getName(); + TableMetadata tableMetadata = getTableMetadata(session, tableHandle); + CatalogSchemaTableName sourceTableName = new CatalogSchemaTableName(catalogHandle.getCatalogName(), tableMetadata.getTable()); + systemSecurityMetadata.columnDropped(session, sourceTableName, columnName); + } } @Override diff --git a/core/trino-main/src/main/java/io/trino/metadata/SystemSecurityMetadata.java b/core/trino-main/src/main/java/io/trino/metadata/SystemSecurityMetadata.java index 7faa8d06d795..b8ba4de03796 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/SystemSecurityMetadata.java +++ b/core/trino-main/src/main/java/io/trino/metadata/SystemSecurityMetadata.java @@ -167,4 +167,19 @@ public interface SystemSecurityMetadata * A table or view was dropped */ void tableDropped(Session session, CatalogSchemaTableName table); + + /** + * A column was created + */ + void columnCreated(Session session, CatalogSchemaTableName table, String column); + + /** + * A column was renamed + */ + void columnRenamed(Session session, CatalogSchemaTableName table, String oldColumn, String newColumn); + + /** + * A column was dropped + */ + void columnDropped(Session session, CatalogSchemaTableName table, String column); } diff --git a/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClient.java b/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClient.java index 5eed6d8fe2d6..85c726045e13 100644 --- a/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClient.java +++ b/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClient.java @@ -19,6 +19,7 @@ import io.airlift.http.client.HttpClient; import io.airlift.log.Logger; import io.airlift.slice.Slice; +import io.airlift.stats.TDigest; import io.airlift.units.DataSize; import io.airlift.units.Duration; import io.trino.FeaturesConfig.DataIntegrityVerification; @@ -27,6 +28,7 @@ import io.trino.memory.context.LocalMemoryContext; import io.trino.operator.HttpPageBufferClient.ClientCallback; import io.trino.operator.WorkProcessor.ProcessState; +import io.trino.plugin.base.metrics.TDigestHistogram; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -83,6 +85,8 @@ public class DirectExchangeClient private long averageBytesPerRequest; @GuardedBy("this") private boolean closed; + @GuardedBy("this") + private final TDigest requestDuration = new TDigest(); @GuardedBy("memoryContextLock") @Nullable @@ -143,7 +147,8 @@ public DirectExchangeClientStatus getStatus() buffer.getSpilledPageCount(), buffer.getSpilledBytes(), noMoreLocations, - pageBufferClientStatus); + pageBufferClientStatus, + new TDigestHistogram(TDigest.copyOf(requestDuration))); } } @@ -369,6 +374,7 @@ private void releaseMemoryContext() private synchronized void requestComplete(HttpPageBufferClient client) { + requestDuration.add(client.getLastRequestDurationMillis()); if (!completedClients.contains(client) && !queuedClients.contains(client)) { queuedClients.add(client); } diff --git a/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientStatus.java b/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientStatus.java index 6b130814e9f0..4d1bf5efd568 100644 --- a/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientStatus.java +++ b/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientStatus.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; +import io.trino.plugin.base.metrics.TDigestHistogram; import io.trino.spi.Mergeable; import java.util.List; @@ -35,6 +36,7 @@ public class DirectExchangeClientStatus private final long spilledBytes; private final boolean noMoreLocations; private final List pageBufferClientStatuses; + private final TDigestHistogram requestDuration; @JsonCreator public DirectExchangeClientStatus( @@ -46,7 +48,8 @@ public DirectExchangeClientStatus( @JsonProperty("spilledPages") int spilledPages, @JsonProperty("spilledBytes") long spilledBytes, @JsonProperty("noMoreLocations") boolean noMoreLocations, - @JsonProperty("pageBufferClientStatuses") List pageBufferClientStatuses) + @JsonProperty("pageBufferClientStatuses") List pageBufferClientStatuses, + @JsonProperty("requestDuration") TDigestHistogram requestDuration) { this.bufferedBytes = bufferedBytes; this.maxBufferedBytes = maxBufferedBytes; @@ -57,6 +60,7 @@ public DirectExchangeClientStatus( this.spilledBytes = spilledBytes; this.noMoreLocations = noMoreLocations; this.pageBufferClientStatuses = ImmutableList.copyOf(requireNonNull(pageBufferClientStatuses, "pageBufferClientStatuses is null")); + this.requestDuration = requireNonNull(requestDuration, "requestsDuration is null"); } @JsonProperty @@ -113,6 +117,12 @@ public List getPageBufferClientStatuses() return pageBufferClientStatuses; } + @JsonProperty + public TDigestHistogram getRequestDuration() + { + return requestDuration; + } + @Override public boolean isFinal() { @@ -132,6 +142,7 @@ public String toString() .add("spilledBytes", spilledBytes) .add("noMoreLocations", noMoreLocations) .add("pageBufferClientStatuses", pageBufferClientStatuses) + .add("requestDuration", requestDuration) .toString(); } @@ -147,7 +158,8 @@ public DirectExchangeClientStatus mergeWith(DirectExchangeClientStatus other) spilledPages + other.spilledPages, spilledBytes + other.spilledBytes, noMoreLocations && other.noMoreLocations, // if at least one has some locations, mergee has some too - ImmutableList.of()); // pageBufferClientStatuses may be long, so we don't want to combine the lists + ImmutableList.of(), // pageBufferClientStatuses may be long, so we don't want to combine the lists + requestDuration.mergeWith(other.requestDuration)); // this is correct as long as all clients have the same shape of histogram } private static long mergeAvgs(long value1, long count1, long value2, long count2) diff --git a/core/trino-main/src/main/java/io/trino/operator/Driver.java b/core/trino-main/src/main/java/io/trino/operator/Driver.java index 03ae061225c7..c525f2d8c523 100644 --- a/core/trino-main/src/main/java/io/trino/operator/Driver.java +++ b/core/trino-main/src/main/java/io/trino/operator/Driver.java @@ -336,6 +336,7 @@ private OperationTimer createTimer() driverContext.isCpuTimerEnabled() && driverContext.isPerOperatorCpuTimerEnabled()); } + // sourceBlockedFuture rezprezentuje blokade private ListenableFuture updateDriverBlockedFuture(ListenableFuture sourceBlockedFuture) { // driverBlockedFuture will be completed as soon as the sourceBlockedFuture is completed 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 34b1b5d58948..c5a6cf95682d 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 @@ -232,6 +232,8 @@ public void addInput(Page page) @Override public Page getOutput() { + System.out.println("ExchangeOperator::isBlocked. My id is %s, exchangeDataSource id is %s".formatted(this, exchangeDataSource)); + Slice page = exchangeDataSource.pollPage(); if (page == null) { return null; diff --git a/core/trino-main/src/main/java/io/trino/operator/HttpPageBufferClient.java b/core/trino-main/src/main/java/io/trino/operator/HttpPageBufferClient.java index 3e9b0dcfd72c..66004b83a462 100644 --- a/core/trino-main/src/main/java/io/trino/operator/HttpPageBufferClient.java +++ b/core/trino-main/src/main/java/io/trino/operator/HttpPageBufferClient.java @@ -145,7 +145,8 @@ public interface ClientCallback private boolean completed; @GuardedBy("this") private String taskInstanceId; - + private volatile long lastRequestStartNanos; + private volatile long lastRequestDurationMillis; // it is synchronized on `this` for update private volatile long averageRequestSizeInBytes; @@ -161,6 +162,7 @@ public interface ClientCallback private final AtomicInteger requestsFailed = new AtomicInteger(); private final Executor pageBufferClientCallbackExecutor; + private final Ticker ticker; public HttpPageBufferClient( String selfAddress, @@ -217,6 +219,7 @@ public HttpPageBufferClient( requireNonNull(maxErrorDuration, "maxErrorDuration is null"); requireNonNull(ticker, "ticker is null"); this.backoff = new Backoff(maxErrorDuration, ticker); + this.ticker = ticker; } public synchronized PageBufferClientStatus getStatus() @@ -327,6 +330,11 @@ public synchronized void scheduleRequest() requestsScheduled.incrementAndGet(); } + public long getLastRequestDurationMillis() + { + return lastRequestDurationMillis; + } + private synchronized void initiateRequest() { scheduled = false; @@ -347,6 +355,7 @@ private synchronized void initiateRequest() private synchronized void sendGetResults() { URI uri = HttpUriBuilder.uriBuilderFrom(location).appendPath(String.valueOf(token)).build(); + lastRequestStartNanos = ticker.read(); HttpResponseFuture resultFuture = httpClient.executeAsync( prepareGet() .setHeader(TRINO_MAX_SIZE, maxResponseSize.toString()) @@ -360,7 +369,7 @@ private synchronized void sendGetResults() public void onSuccess(PagesResponse result) { assertNotHoldsLock(this); - + lastRequestDurationMillis = (ticker.read() - lastRequestStartNanos) / 1_000_000; backoff.success(); List pages; @@ -467,6 +476,8 @@ public void onFailure(Throwable t) log.debug("Request to %s failed %s", uri, t); assertNotHoldsLock(this); + lastRequestDurationMillis = (ticker.read() - lastRequestStartNanos) / 1_000_000; + if (t instanceof ChecksumVerificationException) { switch (dataIntegrityVerification) { case NONE: diff --git a/core/trino-main/src/main/java/io/trino/operator/TaskContext.java b/core/trino-main/src/main/java/io/trino/operator/TaskContext.java index a55111fdfed8..b8380ad23afa 100644 --- a/core/trino-main/src/main/java/io/trino/operator/TaskContext.java +++ b/core/trino-main/src/main/java/io/trino/operator/TaskContext.java @@ -31,6 +31,7 @@ import io.trino.execution.buffer.LazyOutputBuffer; import io.trino.memory.QueryContext; import io.trino.memory.QueryContextVisitor; +import io.trino.memory.context.AggregatedMemoryContext; import io.trino.memory.context.LocalMemoryContext; import io.trino.memory.context.MemoryTrackingContext; import io.trino.spi.predicate.Domain; @@ -43,9 +44,11 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -92,6 +95,8 @@ public class TaskContext private final Object cumulativeMemoryLock = new Object(); private final AtomicDouble cumulativeUserMemory = new AtomicDouble(0.0); + private final AtomicInteger maxWriterCount = new AtomicInteger(-1); + @GuardedBy("cumulativeMemoryLock") private long lastUserMemoryReservation; @@ -285,6 +290,11 @@ public LocalMemoryContext localMemoryContext() return taskMemoryContext.localUserMemoryContext(); } + public AggregatedMemoryContext newAggregateMemoryContext() + { + return taskMemoryContext.newAggregateUserMemoryContext(); + } + public boolean isPerOperatorCpuTimerEnabled() { return perOperatorCpuTimerEnabled; @@ -349,6 +359,20 @@ public long getPhysicalWrittenDataSize() return physicalWrittenBytes; } + public void setMaxWriterCount(int maxWriterCount) + { + checkArgument(maxWriterCount > 0, "maxWriterCount must be > 0"); + + int oldMaxWriterCount = this.maxWriterCount.getAndSet(maxWriterCount); + checkArgument(oldMaxWriterCount == -1 || oldMaxWriterCount == maxWriterCount, "maxWriterCount already set to " + oldMaxWriterCount); + } + + public Optional getMaxWriterCount() + { + int value = maxWriterCount.get(); + return value == -1 ? Optional.empty() : Optional.of(value); + } + public Duration getFullGcTime() { long startFullGcTimeNanos = this.startFullGcTimeNanos.get(); @@ -570,6 +594,7 @@ public TaskStats getTaskStats() outputPositions, new Duration(outputBlockedTime, NANOSECONDS).convertToMostSuccinctTimeUnit(), succinctBytes(physicalWrittenDataSize), + getMaxWriterCount(), fullGcCount, fullGcTime, pipelineStats); diff --git a/core/trino-main/src/main/java/io/trino/operator/TaskStats.java b/core/trino-main/src/main/java/io/trino/operator/TaskStats.java index 1cd7b01c1a53..be7bcb0067f4 100644 --- a/core/trino-main/src/main/java/io/trino/operator/TaskStats.java +++ b/core/trino-main/src/main/java/io/trino/operator/TaskStats.java @@ -24,6 +24,7 @@ import javax.annotation.Nullable; import java.util.List; +import java.util.Optional; import java.util.Set; import static com.google.common.base.Preconditions.checkArgument; @@ -83,6 +84,7 @@ public class TaskStats private final Duration outputBlockedTime; private final DataSize physicalWrittenDataSize; + private final Optional maxWriterCount; private final int fullGcCount; private final Duration fullGcTime; @@ -130,6 +132,7 @@ public TaskStats(DateTime createTime, DateTime endTime) 0, new Duration(0, MILLISECONDS), DataSize.ofBytes(0), + Optional.empty(), 0, new Duration(0, MILLISECONDS), ImmutableList.of()); @@ -187,6 +190,7 @@ public TaskStats( @JsonProperty("outputBlockedTime") Duration outputBlockedTime, @JsonProperty("physicalWrittenDataSize") DataSize physicalWrittenDataSize, + @JsonProperty("writerCount") Optional writerCount, @JsonProperty("fullGcCount") int fullGcCount, @JsonProperty("fullGcTime") Duration fullGcTime, @@ -260,6 +264,7 @@ public TaskStats( this.outputBlockedTime = requireNonNull(outputBlockedTime, "outputBlockedTime is null"); this.physicalWrittenDataSize = requireNonNull(physicalWrittenDataSize, "physicalWrittenDataSize is null"); + this.maxWriterCount = requireNonNull(writerCount, "writerCount is null"); checkArgument(fullGcCount >= 0, "fullGcCount is negative"); this.fullGcCount = fullGcCount; @@ -482,6 +487,12 @@ public DataSize getPhysicalWrittenDataSize() return physicalWrittenDataSize; } + @JsonProperty + public Optional getMaxWriterCount() + { + return maxWriterCount; + } + @JsonProperty public List getPipelines() { @@ -566,6 +577,7 @@ public TaskStats summarize() outputPositions, outputBlockedTime, physicalWrittenDataSize, + maxWriterCount, fullGcCount, fullGcTime, ImmutableList.of()); @@ -613,6 +625,7 @@ public TaskStats summarizeFinal() outputPositions, outputBlockedTime, physicalWrittenDataSize, + maxWriterCount, fullGcCount, fullGcTime, summarizePipelineStats(pipelines)); diff --git a/core/trino-main/src/main/java/io/trino/operator/exchange/LocalExchange.java b/core/trino-main/src/main/java/io/trino/operator/exchange/LocalExchange.java index 5915f30399e9..2e94a11f746f 100644 --- a/core/trino-main/src/main/java/io/trino/operator/exchange/LocalExchange.java +++ b/core/trino-main/src/main/java/io/trino/operator/exchange/LocalExchange.java @@ -285,7 +285,7 @@ private static PartitionFunction createPartitionFunction( // The same bucket function (with the same bucket count) as for node // partitioning must be used. This way rows within a single bucket // will be being processed by single thread. - int bucketCount = nodePartitioningManager.getBucketCount(session, partitioning); + int bucketCount = getBucketCount(session, nodePartitioningManager, partitioning); int[] bucketToPartition = new int[bucketCount]; for (int bucket = 0; bucket < bucketCount; bucket++) { @@ -306,6 +306,15 @@ private static PartitionFunction createPartitionFunction( bucketToPartition); } + public static int getBucketCount(Session session, NodePartitioningManager nodePartitioningManager, PartitioningHandle partitioning) + { + if (partitioning.getConnectorHandle() instanceof MergePartitioningHandle) { + // TODO: can we always use this code path? + return nodePartitioningManager.getNodePartitioningMap(session, partitioning).getBucketToPartition().length; + } + return nodePartitioningManager.getBucketNodeMap(session, partitioning).getBucketCount(); + } + private static boolean isSystemPartitioning(PartitioningHandle partitioning) { return partitioning.getConnectorHandle() instanceof SystemPartitioningHandle; diff --git a/core/trino-main/src/main/java/io/trino/operator/join/NestedLoopJoinOperator.java b/core/trino-main/src/main/java/io/trino/operator/join/NestedLoopJoinOperator.java index 20ff30a1ad35..534c932d47a3 100644 --- a/core/trino-main/src/main/java/io/trino/operator/join/NestedLoopJoinOperator.java +++ b/core/trino-main/src/main/java/io/trino/operator/join/NestedLoopJoinOperator.java @@ -279,7 +279,8 @@ static NestedLoopOutputIterator createNestedLoopOutputIterator(Page probePage, P // bi-morphic parent class for the two implementations allowed. Adding a third implementation will make getOutput megamorphic and // should be avoided @VisibleForTesting - abstract static class NestedLoopOutputIterator + abstract static sealed class NestedLoopOutputIterator + permits PageRepeatingIterator, NestedLoopPageBuilder { public abstract boolean hasNext(); diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PagePartitioner.java b/core/trino-main/src/main/java/io/trino/operator/output/PagePartitioner.java index aba8b1fd62a6..0a002032654c 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/PagePartitioner.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/PagePartitioner.java @@ -15,12 +15,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.primitives.Ints; -import com.google.common.util.concurrent.ListenableFuture; import io.airlift.slice.Slice; import io.airlift.units.DataSize; import io.trino.execution.buffer.OutputBuffer; import io.trino.execution.buffer.PageSerializer; import io.trino.execution.buffer.PagesSerdeFactory; +import io.trino.memory.context.AggregatedMemoryContext; +import io.trino.memory.context.LocalMemoryContext; import io.trino.operator.OperatorContext; import io.trino.operator.PartitionFunction; import io.trino.spi.Page; @@ -62,6 +63,7 @@ public class PagePartitioner private final Type[] sourceTypes; private final PartitionFunction partitionFunction; private final int[] partitionChannels; + private final LocalMemoryContext memoryContext; @Nullable private final Block[] partitionConstantBlocks; // when null, no constants are present. Only non-null elements are constants private final PageSerializer serializer; @@ -69,9 +71,8 @@ public class PagePartitioner private final PositionsAppenderPageBuilder[] positionsAppenders; private final boolean replicatesAnyRow; private final int nullChannel; // when >= 0, send the position to every partition if this channel is null - private final AtomicLong rowsAdded = new AtomicLong(); - private final AtomicLong pagesAdded = new AtomicLong(); - private final OperatorContext operatorContext; + private final long partitionsInitialRetainedSize; + private PartitionedOutputInfoSupplier partitionedOutputInfoSupplier; private boolean hasAnyRowBeenReplicated; @@ -85,9 +86,9 @@ public PagePartitioner( PagesSerdeFactory serdeFactory, List sourceTypes, DataSize maxMemory, - OperatorContext operatorContext, PositionsAppenderFactory positionsAppenderFactory, - Optional exchangeEncryptionKey) + Optional exchangeEncryptionKey, + AggregatedMemoryContext aggregatedMemoryContext) { this.partitionFunction = requireNonNull(partitionFunction, "partitionFunction is null"); this.partitionChannels = Ints.toArray(requireNonNull(partitionChannels, "partitionChannels is null")); @@ -106,7 +107,6 @@ public PagePartitioner( this.outputBuffer = requireNonNull(outputBuffer, "outputBuffer is null"); this.sourceTypes = sourceTypes.toArray(new Type[0]); this.serializer = serdeFactory.createSerializer(exchangeEncryptionKey.map(Ciphers::deserializeAesEncryptionKey)); - this.operatorContext = requireNonNull(operatorContext, "operatorContext is null"); // Ensure partition channels align with constant arguments provided for (int i = 0; i < this.partitionChannels.length; i++) { @@ -128,55 +128,17 @@ public PagePartitioner( for (int i = 0; i < partitionCount; i++) { pageBuilders[i] = PageBuilder.withMaxPageSize(pageSize, sourceTypes); } + this.memoryContext = aggregatedMemoryContext.newLocalMemoryContext(PagePartitioner.class.getSimpleName()); + this.partitionsInitialRetainedSize = getRetainedSizeInBytes(); + this.memoryContext.setBytes(partitionsInitialRetainedSize); } - public ListenableFuture isFull() + // sets up this partitioner for the new operator + public void setupOperator(OperatorContext operatorContext) { - return outputBuffer.isFull(); - } - - public long getSizeInBytes() - { - // We use a foreach loop instead of streams - // as it has much better performance. - long sizeInBytes = 0; - for (PositionsAppenderPageBuilder pageBuilder : positionsAppenders) { - sizeInBytes += pageBuilder.getSizeInBytes(); - } - for (PageBuilder pageBuilder : pageBuilders) { - sizeInBytes += pageBuilder.getSizeInBytes(); - } - return sizeInBytes; - } - - /** - * This method can be expensive for complex types. - */ - public long getRetainedSizeInBytes() - { - long sizeInBytes = 0; - for (PositionsAppenderPageBuilder pageBuilder : positionsAppenders) { - sizeInBytes += pageBuilder.getRetainedSizeInBytes(); - } - for (PageBuilder pageBuilder : pageBuilders) { - sizeInBytes += pageBuilder.getRetainedSizeInBytes(); - } - sizeInBytes += serializer.getRetainedSizeInBytes(); - return sizeInBytes; - } - - public Supplier getOperatorInfoSupplier() - { - return createPartitionedOutputOperatorInfoSupplier(rowsAdded, pagesAdded, outputBuffer); - } - - private static Supplier createPartitionedOutputOperatorInfoSupplier(AtomicLong rowsAdded, AtomicLong pagesAdded, OutputBuffer outputBuffer) - { - // Must be a separate static method to avoid embedding references to "this" in the supplier - requireNonNull(rowsAdded, "rowsAdded is null"); - requireNonNull(pagesAdded, "pagesAdded is null"); - requireNonNull(outputBuffer, "outputBuffer is null"); - return () -> new PartitionedOutputInfo(rowsAdded.get(), pagesAdded.get(), outputBuffer.getPeakMemoryUsage()); + // for new operator we need to reset the stats gathered by this PagePartitioner + partitionedOutputInfoSupplier = new PartitionedOutputInfoSupplier(outputBuffer, operatorContext); + operatorContext.setInfoSupplier(partitionedOutputInfoSupplier); } public void partitionPage(Page page) @@ -195,6 +157,18 @@ public void partitionPage(Page page) else { partitionPageByColumn(page); } + updateMemoryUsage(); + } + + private void updateMemoryUsage() + { + // We use getSizeInBytes() here instead of getRetainedSizeInBytes() for an approximation of + // the amount of memory used by the pageBuilders, because calculating the retained + // size can be expensive especially for complex types. + long partitionsSizeInBytes = getSizeInBytes(); + + // We also add partitionsInitialRetainedSize as an approximation of the object overhead of the partitions. + memoryContext.setBytes(partitionsSizeInBytes + partitionsInitialRetainedSize); } public void partitionPageByRow(Page page) @@ -471,10 +445,11 @@ private Page getPartitionFunctionArguments(Page page) return new Page(page.getPositionCount(), blocks); } - public void forceFlush() + public void close() { flushPositionsAppenders(true); flushPageBuilders(true); + memoryContext.close(); } private void flushPageBuilders(boolean force) @@ -505,11 +480,8 @@ private void flushPositionsAppenders(boolean force) private void enqueuePage(Page pagePartition, int partition) { - operatorContext.recordOutput(pagePartition.getSizeInBytes(), pagePartition.getPositionCount()); - outputBuffer.enqueue(partition, splitAndSerializePage(pagePartition)); - pagesAdded.incrementAndGet(); - rowsAdded.addAndGet(pagePartition.getPositionCount()); + partitionedOutputInfoSupplier.recordPage(pagePartition); } private List splitAndSerializePage(Page page) @@ -521,4 +493,67 @@ private List splitAndSerializePage(Page page) } return builder.build(); } + + private long getSizeInBytes() + { + // We use a foreach loop instead of streams + // as it has much better performance. + long sizeInBytes = 0; + for (PositionsAppenderPageBuilder pageBuilder : positionsAppenders) { + sizeInBytes += pageBuilder.getSizeInBytes(); + } + for (PageBuilder pageBuilder : pageBuilders) { + sizeInBytes += pageBuilder.getSizeInBytes(); + } + return sizeInBytes; + } + + /** + * This method can be expensive for complex types. + */ + private long getRetainedSizeInBytes() + { + long sizeInBytes = 0; + for (PositionsAppenderPageBuilder pageBuilder : positionsAppenders) { + sizeInBytes += pageBuilder.getRetainedSizeInBytes(); + } + for (PageBuilder pageBuilder : pageBuilders) { + sizeInBytes += pageBuilder.getRetainedSizeInBytes(); + } + sizeInBytes += serializer.getRetainedSizeInBytes(); + return sizeInBytes; + } + + /** + * Keeps statistics about output pages produced by the partitioner + updates the stats in the operatorContext. + */ + private static class PartitionedOutputInfoSupplier + implements Supplier + { + private final AtomicLong rowsAdded = new AtomicLong(); + private final AtomicLong pagesAdded = new AtomicLong(); + private final OutputBuffer outputBuffer; + private final OperatorContext operatorContext; + + private PartitionedOutputInfoSupplier(OutputBuffer outputBuffer, OperatorContext operatorContext) + { + this.outputBuffer = requireNonNull(outputBuffer, "outputBuffer is null"); + this.operatorContext = requireNonNull(operatorContext, "operatorContext is null"); + } + + @Override + public PartitionedOutputInfo get() + { + // note that outputBuffer.getPeakMemoryUsage() will produce peak across many operators + // this is suboptimal but hard to fix properly + return new PartitionedOutputInfo(rowsAdded.get(), pagesAdded.get(), outputBuffer.getPeakMemoryUsage()); + } + + public void recordPage(Page pagePartition) + { + operatorContext.recordOutput(pagePartition.getSizeInBytes(), pagePartition.getPositionCount()); + pagesAdded.incrementAndGet(); + rowsAdded.addAndGet(pagePartition.getPositionCount()); + } + } } diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PagePartitionerPool.java b/core/trino-main/src/main/java/io/trino/operator/output/PagePartitionerPool.java new file mode 100644 index 000000000000..f50efbc46f8f --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/operator/output/PagePartitionerPool.java @@ -0,0 +1,85 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.operator.output; + +import com.google.common.collect.ImmutableList; + +import javax.annotation.concurrent.GuardedBy; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Queue; +import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +public class PagePartitionerPool +{ + private final Supplier pagePartitionerSupplier; + /** + * Maximum number of free {@link PagePartitioner}s. + * In normal conditions, in the steady state, + * the number of free {@link PagePartitioner}s is going to be close to 0. + * There is a possible case though, where initially big number of concurrent drivers, say 128, + * drops to a small number e.g. 32 in a steady state. This could cause a lot of memory + * to be retained by the unused buffers. + * To defend against that, {@link #maxFree} limits the number of free buffers, + * thus limiting unused memory. + */ + private final int maxFree; + @GuardedBy("this") + private final Queue free = new ArrayDeque<>(); + @GuardedBy("this") + private boolean closed; + + public PagePartitionerPool(int maxFree, Supplier pagePartitionerSupplier) + { + this.maxFree = maxFree; + this.pagePartitionerSupplier = requireNonNull(pagePartitionerSupplier, "pagePartitionerSupplier is null"); + } + + public synchronized PagePartitioner poll() + { + checkArgument(!closed, "The pool is already closed"); + return free.isEmpty() ? pagePartitionerSupplier.get() : free.poll(); + } + + public void release(PagePartitioner pagePartitioner) + { + // pagePartitioner.close can take a long time (flush->serialization), we want to keep it out of the synchronized block + boolean shouldRetain; + synchronized (this) { + shouldRetain = !closed && free.size() < maxFree; + if (shouldRetain) { + free.add(pagePartitioner); + } + } + if (!shouldRetain) { + pagePartitioner.close(); + } + } + + public void close() + { + // pagePartitioner.close can take a long time (flush->serialization), we want to keep it out of the synchronized block + markClosed().forEach(PagePartitioner::close); + } + + private synchronized Collection markClosed() + { + closed = true; + return ImmutableList.copyOf(free); + } +} diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PartitionedOutputOperator.java b/core/trino-main/src/main/java/io/trino/operator/output/PartitionedOutputOperator.java index c1964bfda65a..5b5aa956bcfc 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/PartitionedOutputOperator.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/PartitionedOutputOperator.java @@ -16,10 +16,11 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.util.concurrent.ListenableFuture; +import io.airlift.slice.Slice; import io.airlift.units.DataSize; import io.trino.execution.buffer.OutputBuffer; import io.trino.execution.buffer.PagesSerdeFactory; -import io.trino.memory.context.LocalMemoryContext; +import io.trino.memory.context.AggregatedMemoryContext; import io.trino.operator.DriverContext; import io.trino.operator.Operator; import io.trino.operator.OperatorContext; @@ -39,6 +40,7 @@ import java.util.function.Function; import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; public class PartitionedOutputOperator @@ -55,6 +57,9 @@ public static class PartitionedOutputFactory private final OptionalInt nullChannel; private final DataSize maxMemory; private final PositionsAppenderFactory positionsAppenderFactory; + private final Optional exchangeEncryptionKey; + private final AggregatedMemoryContext memoryContext; + private final int pagePartitionerPoolSize; public PartitionedOutputFactory( PartitionFunction partitionFunction, @@ -64,7 +69,10 @@ public PartitionedOutputFactory( OptionalInt nullChannel, OutputBuffer outputBuffer, DataSize maxMemory, - PositionsAppenderFactory positionsAppenderFactory) + PositionsAppenderFactory positionsAppenderFactory, + Optional exchangeEncryptionKey, + AggregatedMemoryContext memoryContext, + int pagePartitionerPoolSize) { this.partitionFunction = requireNonNull(partitionFunction, "partitionFunction is null"); this.partitionChannels = requireNonNull(partitionChannels, "partitionChannels is null"); @@ -74,6 +82,9 @@ public PartitionedOutputFactory( this.outputBuffer = requireNonNull(outputBuffer, "outputBuffer is null"); this.maxMemory = requireNonNull(maxMemory, "maxMemory is null"); this.positionsAppenderFactory = requireNonNull(positionsAppenderFactory, "positionsAppenderFactory is null"); + this.exchangeEncryptionKey = requireNonNull(exchangeEncryptionKey, "exchangeEncryptionKey is null"); + this.memoryContext = requireNonNull(memoryContext, "memoryContext is null"); + this.pagePartitionerPoolSize = pagePartitionerPoolSize; } @Override @@ -97,7 +108,10 @@ public OperatorFactory createOutputOperator( outputBuffer, serdeFactory, maxMemory, - positionsAppenderFactory); + positionsAppenderFactory, + exchangeEncryptionKey, + memoryContext, + pagePartitionerPoolSize); } } @@ -117,6 +131,10 @@ public static class PartitionedOutputOperatorFactory private final PagesSerdeFactory serdeFactory; private final DataSize maxMemory; private final PositionsAppenderFactory positionsAppenderFactory; + private final Optional exchangeEncryptionKey; + private final AggregatedMemoryContext memoryContext; + private final int pagePartitionerPoolSize; + private final PagePartitionerPool pagePartitionerPool; public PartitionedOutputOperatorFactory( int operatorId, @@ -131,7 +149,10 @@ public PartitionedOutputOperatorFactory( OutputBuffer outputBuffer, PagesSerdeFactory serdeFactory, DataSize maxMemory, - PositionsAppenderFactory positionsAppenderFactory) + PositionsAppenderFactory positionsAppenderFactory, + Optional exchangeEncryptionKey, + AggregatedMemoryContext memoryContext, + int pagePartitionerPoolSize) { this.operatorId = operatorId; this.planNodeId = requireNonNull(planNodeId, "planNodeId is null"); @@ -146,6 +167,24 @@ public PartitionedOutputOperatorFactory( this.serdeFactory = requireNonNull(serdeFactory, "serdeFactory is null"); this.maxMemory = requireNonNull(maxMemory, "maxMemory is null"); this.positionsAppenderFactory = requireNonNull(positionsAppenderFactory, "positionsAppenderFactory is null"); + this.exchangeEncryptionKey = requireNonNull(exchangeEncryptionKey, "exchangeEncryptionKey is null"); + this.memoryContext = requireNonNull(memoryContext, "memoryContext is null"); + this.pagePartitionerPoolSize = pagePartitionerPoolSize; + this.pagePartitionerPool = new PagePartitionerPool( + pagePartitionerPoolSize, + () -> new PagePartitioner( + partitionFunction, + partitionChannels, + partitionConstants, + replicatesAnyRow, + nullChannel, + outputBuffer, + serdeFactory, + sourceTypes, + maxMemory, + positionsAppenderFactory, + exchangeEncryptionKey, + memoryContext)); } @Override @@ -154,22 +193,15 @@ public Operator createOperator(DriverContext driverContext) OperatorContext operatorContext = driverContext.addOperatorContext(operatorId, planNodeId, PartitionedOutputOperator.class.getSimpleName()); return new PartitionedOutputOperator( operatorContext, - sourceTypes, pagePreprocessor, - partitionFunction, - partitionChannels, - partitionConstants, - replicatesAnyRow, - nullChannel, outputBuffer, - serdeFactory, - maxMemory, - positionsAppenderFactory); + pagePartitionerPool); } @Override public void noMoreOperators() { + pagePartitionerPool.close(); } @Override @@ -188,52 +220,34 @@ public OperatorFactory duplicate() outputBuffer, serdeFactory, maxMemory, - positionsAppenderFactory); + positionsAppenderFactory, + exchangeEncryptionKey, + memoryContext, + pagePartitionerPoolSize); } } private final OperatorContext operatorContext; private final Function pagePreprocessor; + private final PagePartitionerPool pagePartitionerPool; private final PagePartitioner partitionFunction; - private final LocalMemoryContext memoryContext; - private final long partitionsInitialRetainedSize; + // outputBuffer is used only to block the operator from finishing if the outputBuffer is full + private final OutputBuffer outputBuffer; private ListenableFuture isBlocked = NOT_BLOCKED; private boolean finished; public PartitionedOutputOperator( OperatorContext operatorContext, - List sourceTypes, Function pagePreprocessor, - PartitionFunction partitionFunction, - List partitionChannels, - List> partitionConstants, - boolean replicatesAnyRow, - OptionalInt nullChannel, OutputBuffer outputBuffer, - PagesSerdeFactory serdeFactory, - DataSize maxMemory, - PositionsAppenderFactory positionsAppenderFactory) + PagePartitionerPool pagePartitionerPool) { this.operatorContext = requireNonNull(operatorContext, "operatorContext is null"); this.pagePreprocessor = requireNonNull(pagePreprocessor, "pagePreprocessor is null"); - this.partitionFunction = new PagePartitioner( - partitionFunction, - partitionChannels, - partitionConstants, - replicatesAnyRow, - nullChannel, - outputBuffer, - serdeFactory, - sourceTypes, - maxMemory, - operatorContext, - positionsAppenderFactory, - operatorContext.getSession().getExchangeEncryptionKey()); - - operatorContext.setInfoSupplier(this.partitionFunction.getOperatorInfoSupplier()); - this.memoryContext = operatorContext.newLocalUserMemoryContext(PartitionedOutputOperator.class.getSimpleName()); - this.partitionsInitialRetainedSize = this.partitionFunction.getRetainedSizeInBytes(); - this.memoryContext.setBytes(partitionsInitialRetainedSize); + this.pagePartitionerPool = requireNonNull(pagePartitionerPool, "pagePartitionerPool is null"); + this.outputBuffer = requireNonNull(outputBuffer, "outputBuffer is null"); + this.partitionFunction = requireNonNull(pagePartitionerPool.poll(), "partitionFunction is null"); + this.partitionFunction.setupOperator(operatorContext); } @Override @@ -245,8 +259,10 @@ public OperatorContext getOperatorContext() @Override public void finish() { - finished = true; - partitionFunction.forceFlush(); + if (!finished) { + pagePartitionerPool.release(partitionFunction); + finished = true; + } } @Override @@ -255,12 +271,20 @@ public boolean isFinished() return finished && isBlocked().isDone(); } + @Override + public void close() + throws Exception + { + // make sure the operator is finished and partitionFunction released + finish(); + } + @Override public ListenableFuture isBlocked() { // Avoid re-synchronizing on the output buffer when operator is already blocked if (isBlocked.isDone()) { - isBlocked = partitionFunction.isFull(); + isBlocked = outputBuffer.isFull(); if (isBlocked.isDone()) { isBlocked = NOT_BLOCKED; } @@ -278,6 +302,7 @@ public boolean needsInput() public void addInput(Page page) { requireNonNull(page, "page is null"); + checkState(!finished); if (page.getPositionCount() == 0) { return; @@ -285,14 +310,6 @@ public void addInput(Page page) page = pagePreprocessor.apply(page); partitionFunction.partitionPage(page); - - // We use getSizeInBytes() here instead of getRetainedSizeInBytes() for an approximation of - // the amount of memory used by the pageBuilders, because calculating the retained - // size can be expensive especially for complex types. - long partitionsSizeInBytes = partitionFunction.getSizeInBytes(); - - // We also add partitionsInitialRetainedSize as an approximation of the object overhead of the partitions. - memoryContext.setBytes(partitionsSizeInBytes + partitionsInitialRetainedSize); } @Override @@ -301,12 +318,6 @@ public Page getOutput() return null; } - @Override - public void close() - { - memoryContext.close(); - } - public static class PartitionedOutputInfo implements Mergeable, OperatorInfo { diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java index 9920ac65bf80..2ca69e48f9c1 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java @@ -89,7 +89,6 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.LinkedHashMap; @@ -112,6 +111,7 @@ import static java.lang.Boolean.FALSE; import static java.lang.String.format; import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; @@ -212,6 +212,7 @@ public class Analysis private final Multiset rowFilterScopes = HashMultiset.create(); private final Map, List> rowFilters = new LinkedHashMap<>(); + private final Map, List> checkConstraints = new LinkedHashMap<>(); private final Multiset columnMaskScopes = HashMultiset.create(); private final Map, Map> columnMasks = new LinkedHashMap<>(); @@ -1071,9 +1072,20 @@ public void addRowFilter(Table table, Expression filter) .add(filter); } + public void addCheckConstraints(Table table, Expression constraint) + { + checkConstraints.computeIfAbsent(NodeRef.of(table), node -> new ArrayList<>()) + .add(constraint); + } + public List getRowFilters(Table node) { - return rowFilters.getOrDefault(NodeRef.of(node), ImmutableList.of()); + return unmodifiableList(rowFilters.getOrDefault(NodeRef.of(node), ImmutableList.of())); + } + + public List getCheckConstraints(Table node) + { + return unmodifiableList(checkConstraints.getOrDefault(NodeRef.of(node), ImmutableList.of())); } public boolean hasColumnMask(QualifiedObjectName table, String column, String identity) @@ -1101,7 +1113,7 @@ public void addColumnMask(Table table, String column, Expression mask) public Map getColumnMasks(Table table) { - return columnMasks.getOrDefault(NodeRef.of(table), ImmutableMap.of()); + return unmodifiableMap(columnMasks.getOrDefault(NodeRef.of(table), ImmutableMap.of())); } public List getReferencedTables() @@ -1571,22 +1583,22 @@ public void addQuantifiedComparisons(List expres public List getInPredicatesSubqueries() { - return Collections.unmodifiableList(inPredicatesSubqueries); + return unmodifiableList(inPredicatesSubqueries); } public List getSubqueries() { - return Collections.unmodifiableList(subqueries); + return unmodifiableList(subqueries); } public List getExistsSubqueries() { - return Collections.unmodifiableList(existsSubqueries); + return unmodifiableList(existsSubqueries); } public List getQuantifiedComparisonSubqueries() { - return Collections.unmodifiableList(quantifiedComparisonSubqueries); + return unmodifiableList(quantifiedComparisonSubqueries); } } diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java index 4a4924604e45..28bdf9a97af0 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java @@ -226,7 +226,7 @@ import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimeType.TIME_MILLIS; import static io.trino.spi.type.TimeType.createTimeType; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimeWithTimeZoneType.createTimeWithTimeZoneType; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampType.createTimestampType; @@ -627,7 +627,7 @@ protected Type visitCurrentTime(CurrentTime node, StackableAstVisitorContext { if (node.getPrecision() != null) { diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index d1c77fe68921..854edf086328 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -115,7 +115,6 @@ import io.trino.sql.analyzer.Scope.AsteriskedIdentifierChainBasis; import io.trino.sql.parser.ParsingException; import io.trino.sql.parser.SqlParser; -import io.trino.sql.planner.DeterminismEvaluator; import io.trino.sql.planner.ExpressionInterpreter; import io.trino.sql.planner.PartitioningHandle; import io.trino.sql.planner.ScopeAware; @@ -296,6 +295,7 @@ import static io.trino.spi.StandardErrorCode.FUNCTION_NOT_FOUND; import static io.trino.spi.StandardErrorCode.FUNCTION_NOT_WINDOW; import static io.trino.spi.StandardErrorCode.INVALID_ARGUMENTS; +import static io.trino.spi.StandardErrorCode.INVALID_CHECK_CONSTRAINT; import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_REFERENCE; import static io.trino.spi.StandardErrorCode.INVALID_COPARTITIONING; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; @@ -364,6 +364,8 @@ import static io.trino.sql.analyzer.SemanticExceptions.semanticException; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static io.trino.sql.analyzer.TypeSignatureTranslator.toTypeSignature; +import static io.trino.sql.planner.DeterminismEvaluator.containsCurrentTimeFunctions; +import static io.trino.sql.planner.DeterminismEvaluator.isDeterministic; import static io.trino.sql.planner.ExpressionInterpreter.evaluateConstantExpression; import static io.trino.sql.tree.BooleanLiteral.TRUE_LITERAL; import static io.trino.sql.tree.DereferenceExpression.getQualifiedName; @@ -541,6 +543,7 @@ protected Scope visitInsert(Insert insert, Optional scope) List columns = tableSchema.getColumns().stream() .filter(column -> !column.isHidden()) .collect(toImmutableList()); + List checkConstraints = tableSchema.getTableSchema().getCheckConstraints(); for (ColumnSchema column : columns) { if (accessControl.getColumnMask(session.toSecurityContext(), targetTable, column.getName(), column.getType()).isPresent()) { @@ -550,7 +553,12 @@ protected Scope visitInsert(Insert insert, Optional scope) Map columnHandles = metadata.getColumnHandles(session, targetTableHandle.get()); List tableFields = analyzeTableOutputFields(insert.getTable(), targetTable, tableSchema, columnHandles); - analyzeFiltersAndMasks(insert.getTable(), targetTable, targetTableHandle, tableFields, session.getIdentity().getUser()); + Scope accessControlScope = Scope.builder() + .withRelationType(RelationId.anonymous(), new RelationType(tableFields)) + .build(); + analyzeFiltersAndMasks(insert.getTable(), targetTable, new RelationType(tableFields), accessControlScope); + analyzeCheckConstraints(insert.getTable(), targetTable, accessControlScope, checkConstraints); + analysis.registerTable(insert.getTable(), targetTableHandle, targetTable, session.getIdentity().getUser(), accessControlScope); List tableColumns = columns.stream() .map(ColumnSchema::getName) @@ -618,9 +626,9 @@ protected Scope visitInsert(Insert insert, Optional scope) targetTable, Optional.empty(), Optional.of(Streams.zip( - columnStream, - queryScope.getRelationType().getVisibleFields().stream(), - (column, field) -> new OutputColumn(column, analysis.getSourceColumns(field))) + columnStream, + queryScope.getRelationType().getVisibleFields().stream(), + (column, field) -> new OutputColumn(column, analysis.getSourceColumns(field))) .collect(toImmutableList()))); return createAndAssignScope(insert, scope, Field.newUnqualified("rows", BIGINT)); @@ -701,9 +709,9 @@ protected Scope visitRefreshMaterializedView(RefreshMaterializedView refreshMate targetTable, Optional.empty(), Optional.of(Streams.zip( - columns, - queryScope.getRelationType().getVisibleFields().stream(), - (column, field) -> new OutputColumn(column, analysis.getSourceColumns(field))) + columns, + queryScope.getRelationType().getVisibleFields().stream(), + (column, field) -> new OutputColumn(column, analysis.getSourceColumns(field))) .collect(toImmutableList()))); return createAndAssignScope(refreshMaterializedView, scope, Field.newUnqualified("rows", BIGINT)); @@ -800,7 +808,12 @@ protected Scope visitDelete(Delete node, Optional scope) analysis.setUpdateType("DELETE"); analysis.setUpdateTarget(tableName, Optional.of(table), Optional.empty()); - analyzeFiltersAndMasks(table, tableName, Optional.of(handle), analysis.getScope(table).getRelationType(), session.getIdentity().getUser()); + Scope accessControlScope = Scope.builder() + .withRelationType(RelationId.anonymous(), analysis.getScope(table).getRelationType()) + .build(); + analyzeFiltersAndMasks(table, tableName, analysis.getScope(table).getRelationType(), accessControlScope); + analyzeCheckConstraints(table, tableName, accessControlScope, tableSchema.getTableSchema().getCheckConstraints()); + analysis.registerTable(table, Optional.of(handle), tableName, session.getIdentity().getUser(), accessControlScope); createMergeAnalysis(table, handle, tableSchema, tableScope, tableScope, ImmutableList.of()); @@ -1180,10 +1193,10 @@ protected Scope visitTableExecute(TableExecute node, Optional scope) TableExecuteHandle executeHandle = metadata.getTableHandleForExecute( - session, - tableHandle, - procedureName, - tableProperties) + session, + tableHandle, + procedureName, + tableProperties) .orElseThrow(() -> semanticException(NOT_SUPPORTED, node, "Procedure '%s' cannot be executed on table '%s'", procedureName, tableName)); analysis.setTableExecuteReadsData(procedureMetadata.getExecutionMode().isReadsData()); @@ -2164,7 +2177,12 @@ protected Scope visitTable(Table table, Optional scope) List outputFields = fields.build(); - analyzeFiltersAndMasks(table, targetTableName, tableHandle, outputFields, session.getIdentity().getUser()); + Scope accessControlScope = Scope.builder() + .withRelationType(RelationId.anonymous(), new RelationType(outputFields)) + .build(); + analyzeFiltersAndMasks(table, targetTableName, new RelationType(outputFields), accessControlScope); + analyzeCheckConstraints(table, targetTableName, accessControlScope, tableSchema.getTableSchema().getCheckConstraints()); + analysis.registerTable(table, tableHandle, targetTableName, session.getIdentity().getUser(), accessControlScope); Scope tableScope = createAndAssignScope(table, scope, outputFields); @@ -2184,17 +2202,8 @@ private void checkStorageTableNotRedirected(QualifiedObjectName source) }); } - private void analyzeFiltersAndMasks(Table table, QualifiedObjectName name, Optional tableHandle, List fields, String authorization) - { - analyzeFiltersAndMasks(table, name, tableHandle, new RelationType(fields), authorization); - } - - private void analyzeFiltersAndMasks(Table table, QualifiedObjectName name, Optional tableHandle, RelationType relationType, String authorization) + private void analyzeFiltersAndMasks(Table table, QualifiedObjectName name, RelationType relationType, Scope accessControlScope) { - Scope accessControlScope = Scope.builder() - .withRelationType(RelationId.anonymous(), relationType) - .build(); - for (int index = 0; index < relationType.getAllFieldCount(); index++) { Field field = relationType.getFieldByIndex(index); if (field.getName().isPresent()) { @@ -2208,8 +2217,14 @@ private void analyzeFiltersAndMasks(Table table, QualifiedObjectName name, Optio accessControl.getRowFilters(session.toSecurityContext(), name) .forEach(filter -> analyzeRowFilter(session.getIdentity().getUser(), table, name, accessControlScope, filter)); + } - analysis.registerTable(table, tableHandle, name, authorization, accessControlScope); + private void analyzeCheckConstraints(Table table, QualifiedObjectName name, Scope accessControlScope, List constraints) + { + for (String constraint : constraints) { + ViewExpression expression = new ViewExpression(session.getIdentity().getUser(), Optional.of(name.getCatalogName()), Optional.of(name.getSchemaName()), constraint); + analyzeCheckConstraint(table, name, accessControlScope, expression); + } } private boolean checkCanSelectFromColumn(QualifiedObjectName name, String column) @@ -2351,13 +2366,21 @@ private Scope createScopeForView( if (storageTable.isPresent()) { List storageTableFields = analyzeStorageTable(table, viewFields, storageTable.get()); - analyzeFiltersAndMasks(table, name, storageTable, viewFields, session.getIdentity().getUser()); + Scope accessControlScope = Scope.builder() + .withRelationType(RelationId.anonymous(), new RelationType(viewFields)) + .build(); + analyzeFiltersAndMasks(table, name, new RelationType(viewFields), accessControlScope); + analysis.registerTable(table, storageTable, name, session.getIdentity().getUser(), accessControlScope); analysis.addRelationCoercion(table, viewFields.stream().map(Field::getType).toArray(Type[]::new)); // use storage table output fields as they contain ColumnHandles return createAndAssignScope(table, scope, storageTableFields); } - analyzeFiltersAndMasks(table, name, storageTable, viewFields, session.getIdentity().getUser()); + Scope accessControlScope = Scope.builder() + .withRelationType(RelationId.anonymous(), new RelationType(viewFields)) + .build(); + analyzeFiltersAndMasks(table, name, new RelationType(viewFields), accessControlScope); + analysis.registerTable(table, storageTable, name, session.getIdentity().getUser(), accessControlScope); viewFields.forEach(field -> analysis.addSourceColumns(field, ImmutableSet.of(new SourceColumn(name, field.getName().orElseThrow())))); analysis.registerNamedQuery(table, query); return createAndAssignScope(table, scope, viewFields); @@ -2775,15 +2798,15 @@ protected Scope visitSampledRelation(SampledRelation relation, Optional s } Map, Type> expressionTypes = ExpressionAnalyzer.analyzeExpressions( - session, - plannerContext, - statementAnalyzerFactory, - accessControl, - TypeProvider.empty(), - ImmutableList.of(samplePercentage), - analysis.getParameters(), - WarningCollector.NOOP, - analysis.getQueryType()) + session, + plannerContext, + statementAnalyzerFactory, + accessControl, + TypeProvider.empty(), + ImmutableList.of(samplePercentage), + analysis.getParameters(), + WarningCollector.NOOP, + analysis.getQueryType()) .getExpressionTypes(); Type samplePercentageType = expressionTypes.get(NodeRef.of(samplePercentage)); @@ -3150,6 +3173,10 @@ protected Scope visitUpdate(Update update, Optional scope) if (!accessControl.getRowFilters(session.toSecurityContext(), tableName).isEmpty()) { throw semanticException(NOT_SUPPORTED, update, "Updating a table with a row filter is not supported"); } + if (!tableSchema.getTableSchema().getCheckConstraints().isEmpty()) { + // TODO https://github.com/trinodb/trino/issues/15411 Add support for CHECK constraint to UPDATE statement + throw semanticException(NOT_SUPPORTED, update, "Updating a table with a check constraint is not supported"); + } // TODO: how to deal with connectors that need to see the pre-image of rows to perform the update without // flowing that data through the masking logic @@ -3277,6 +3304,10 @@ protected Scope visitMerge(Merge merge, Optional scope) if (!accessControl.getRowFilters(session.toSecurityContext(), tableName).isEmpty()) { throw semanticException(NOT_SUPPORTED, merge, "Cannot merge into a table with row filters"); } + if (!tableSchema.getTableSchema().getCheckConstraints().isEmpty()) { + // TODO https://github.com/trinodb/trino/issues/15411 Add support for CHECK constraint to MERGE statement + throw semanticException(NOT_SUPPORTED, merge, "Cannot merge into a table with check constraints"); + } Scope targetTableScope = analyzer.analyzeForUpdate(relation, scope, UpdateKind.MERGE); Scope sourceTableScope = process(merge.getSource(), scope); @@ -4622,6 +4653,62 @@ private void analyzeRowFilter(String currentIdentity, Table table, QualifiedObje analysis.addRowFilter(table, expression); } + private void analyzeCheckConstraint(Table table, QualifiedObjectName name, Scope scope, ViewExpression constraint) + { + Expression expression; + try { + expression = sqlParser.createExpression(constraint.getExpression(), createParsingOptions(session)); + } + catch (ParsingException e) { + throw new TrinoException(INVALID_CHECK_CONSTRAINT, extractLocation(table), format("Invalid check constraint for '%s': %s", name, e.getErrorMessage()), e); + } + + verifyNoAggregateWindowOrGroupingFunctions(session, metadata, expression, format("Check constraint for '%s'", name)); + + ExpressionAnalysis expressionAnalysis; + try { + Identity filterIdentity = Identity.forUser(constraint.getIdentity()) + .withGroups(groupProvider.getGroups(constraint.getIdentity())) + .build(); + expressionAnalysis = ExpressionAnalyzer.analyzeExpression( + createViewSession(constraint.getCatalog(), constraint.getSchema(), filterIdentity, session.getPath()), + plannerContext, + statementAnalyzerFactory, + accessControl, + scope, + analysis, + expression, + warningCollector, + correlationSupport); + } + catch (TrinoException e) { + throw new TrinoException(e::getErrorCode, extractLocation(table), format("Invalid check constraint for '%s': %s", name, e.getRawMessage()), e); + } + + // Ensure that the expression doesn't contain non-deterministic functions. This should be "retrospectively deterministic" per SQL standard. + if (!isDeterministic(expression, this::getResolvedFunction)) { + throw semanticException(INVALID_CHECK_CONSTRAINT, expression, "Check constraint expression should be deterministic"); + } + if (containsCurrentTimeFunctions(expression)) { + throw semanticException(INVALID_CHECK_CONSTRAINT, expression, "Check constraint expression should not contain temporal expression"); + } + + analysis.recordSubqueries(expression, expressionAnalysis); + + Type actualType = expressionAnalysis.getType(expression); + if (!actualType.equals(BOOLEAN)) { + TypeCoercion coercion = new TypeCoercion(plannerContext.getTypeManager()::getType); + + if (!coercion.canCoerce(actualType, BOOLEAN)) { + throw new TrinoException(TYPE_MISMATCH, extractLocation(table), format("Expected check constraint for '%s' to be of type BOOLEAN, but was %s", name, actualType), null); + } + + analysis.addCoercion(expression, BOOLEAN, coercion.isTypeOnlyCoercion(actualType, BOOLEAN)); + } + + analysis.addCheckConstraints(table, expression); + } + private void analyzeColumnMask(String currentIdentity, Table table, QualifiedObjectName tableName, Field field, Scope scope, ViewExpression mask) { String column = field.getName().orElseThrow(); @@ -5007,7 +5094,7 @@ private void verifySelectDistinct(QuerySpecification node, List orde } for (Expression expression : orderByExpressions) { - if (!DeterminismEvaluator.isDeterministic(expression, this::getResolvedFunction)) { + if (!isDeterministic(expression, this::getResolvedFunction)) { throw semanticException(EXPRESSION_NOT_IN_DISTINCT, expression, "Non deterministic ORDER BY expression is not supported with SELECT DISTINCT"); } } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/DeterminismEvaluator.java b/core/trino-main/src/main/java/io/trino/sql/planner/DeterminismEvaluator.java index 25a10a35cb82..ef00948c3acc 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/DeterminismEvaluator.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/DeterminismEvaluator.java @@ -15,6 +15,7 @@ import io.trino.metadata.Metadata; import io.trino.metadata.ResolvedFunction; +import io.trino.sql.tree.CurrentTime; import io.trino.sql.tree.DefaultExpressionTraversalVisitor; import io.trino.sql.tree.Expression; import io.trino.sql.tree.FunctionCall; @@ -65,4 +66,24 @@ protected Void visitFunctionCall(FunctionCall node, AtomicBoolean deterministic) return super.visitFunctionCall(node, deterministic); } } + + public static boolean containsCurrentTimeFunctions(Expression expression) + { + requireNonNull(expression, "expression is null"); + + AtomicBoolean currentTime = new AtomicBoolean(false); + new CurrentTimeVisitor().process(expression, currentTime); + return currentTime.get(); + } + + private static class CurrentTimeVisitor + extends DefaultExpressionTraversalVisitor + { + @Override + protected Void visitCurrentTime(CurrentTime node, AtomicBoolean currentTime) + { + currentTime.set(true); + return super.visitCurrentTime(node, currentTime); + } + } } 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 ff7f39936223..f125ad9aaa01 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 @@ -292,6 +292,7 @@ import static io.trino.SystemSessionProperties.getAggregationOperatorUnspillMemoryLimit; import static io.trino.SystemSessionProperties.getFilterAndProjectMinOutputPageRowCount; import static io.trino.SystemSessionProperties.getFilterAndProjectMinOutputPageSize; +import static io.trino.SystemSessionProperties.getPagePartitioningBufferPoolSize; import static io.trino.SystemSessionProperties.getTaskConcurrency; import static io.trino.SystemSessionProperties.getTaskPartitionedWriterCount; import static io.trino.SystemSessionProperties.getTaskScaleWritersMaxWriterCount; @@ -580,7 +581,10 @@ public LocalExecutionPlan plan( nullChannel, outputBuffer, maxPagePartitioningBufferSize, - positionsAppenderFactory)); + positionsAppenderFactory, + taskContext.getSession().getExchangeEncryptionKey(), + taskContext.newAggregateMemoryContext(), + getPagePartitioningBufferPoolSize(taskContext.getSession()))); } public LocalExecutionPlan plan( @@ -3171,11 +3175,9 @@ public PhysicalOperation visitRefreshMaterializedView(RefreshMaterializedViewNod public PhysicalOperation visitTableWriter(TableWriterNode node, LocalExecutionPlanContext context) { // Set table writer count - context.setDriverInstanceCount(getWriterCount( - session, - node.getPartitioningScheme(), - node.getPreferredPartitioningScheme(), - node.getSource())); + int maxWriterCount = getWriterCount(session, node.getPartitioningScheme(), node.getPreferredPartitioningScheme(), node.getSource()); + context.setDriverInstanceCount(maxWriterCount); + context.taskContext.setMaxWriterCount(maxWriterCount); PhysicalOperation source = node.getSource().accept(this, context); @@ -3331,11 +3333,9 @@ public PhysicalOperation visitSimpleTableExecuteNode(SimpleTableExecuteNode node public PhysicalOperation visitTableExecute(TableExecuteNode node, LocalExecutionPlanContext context) { // Set table writer count - context.setDriverInstanceCount(getWriterCount( - session, - node.getPartitioningScheme(), - node.getPreferredPartitioningScheme(), - node.getSource())); + int maxWriterCount = getWriterCount(session, node.getPartitioningScheme(), node.getPreferredPartitioningScheme(), node.getSource()); + context.setDriverInstanceCount(maxWriterCount); + context.taskContext.setMaxWriterCount(maxWriterCount); PhysicalOperation source = node.getSource().accept(this, context); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java b/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java index 3befb4cc93c2..7e201215047c 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java @@ -496,6 +496,18 @@ private RelationPlan getInsertPlan( .withRelationType(accessControlScope.getRelationId(), accessControlScope.getRelationType().withOnlyVisibleFields()) .build(); }); + plan = planner.addCheckConstraints( + analysis.getCheckConstraints(table), + table, + plan, + node -> { + Scope accessControlScope = analysis.getAccessControlScope(table); + // hidden fields are not accessible in insert + return Scope.builder() + .like(accessControlScope) + .withRelationType(accessControlScope.getRelationId(), accessControlScope.getRelationType().withOnlyVisibleFields()) + .build(); + }); List insertedTableColumnNames = insertedColumns.stream() .map(ColumnMetadata::getName) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/NodePartitioningManager.java b/core/trino-main/src/main/java/io/trino/sql/planner/NodePartitioningManager.java index 1244586b624b..7fd019e203ea 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/NodePartitioningManager.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/NodePartitioningManager.java @@ -51,7 +51,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.SystemSessionProperties.getHashPartitionCount; +import static io.trino.SystemSessionProperties.getMaxHashPartitionCount; import static io.trino.spi.StandardErrorCode.NO_NODES_AVAILABLE; import static io.trino.sql.planner.SystemPartitioningHandle.FIXED_HASH_DISTRIBUTION; import static io.trino.util.Failures.checkCondition; @@ -134,7 +134,12 @@ public BucketFunction getBucketFunction(Session session, PartitioningHandle part public NodePartitionMap getNodePartitioningMap(Session session, PartitioningHandle partitioningHandle) { - return getNodePartitioningMap(session, partitioningHandle, new HashMap<>(), new AtomicReference<>()); + return getNodePartitioningMap(session, partitioningHandle, new HashMap<>(), new AtomicReference<>(), Optional.empty()); + } + + public NodePartitionMap getNodePartitioningMap(Session session, PartitioningHandle partitioningHandle, Optional partitionCount) + { + return getNodePartitioningMap(session, partitioningHandle, new HashMap<>(), new AtomicReference<>(), partitionCount); } /** @@ -145,22 +150,24 @@ private NodePartitionMap getNodePartitioningMap( Session session, PartitioningHandle partitioningHandle, Map> bucketToNodeCache, - AtomicReference> systemPartitioningCache) + AtomicReference> systemPartitioningCache, + Optional partitionCount) { requireNonNull(session, "session is null"); requireNonNull(partitioningHandle, "partitioningHandle is null"); if (partitioningHandle.getConnectorHandle() instanceof SystemPartitioningHandle) { - return systemNodePartitionMap(session, partitioningHandle, systemPartitioningCache); + return systemNodePartitionMap(session, partitioningHandle, systemPartitioningCache, partitionCount); } if (partitioningHandle.getConnectorHandle() instanceof MergePartitioningHandle mergeHandle) { - return mergeHandle.getNodePartitioningMap(handle -> getNodePartitioningMap(session, handle, bucketToNodeCache, systemPartitioningCache)); + return mergeHandle.getNodePartitioningMap(handle -> + getNodePartitioningMap(session, handle, bucketToNodeCache, systemPartitioningCache, partitionCount)); } Optional optionalMap = getConnectorBucketNodeMap(session, partitioningHandle); if (optionalMap.isEmpty()) { - return systemNodePartitionMap(session, FIXED_HASH_DISTRIBUTION, systemPartitioningCache); + return systemNodePartitionMap(session, FIXED_HASH_DISTRIBUTION, systemPartitioningCache, partitionCount); } ConnectorBucketNodeMap connectorBucketNodeMap = optionalMap.get(); @@ -199,7 +206,11 @@ private NodePartitionMap getNodePartitioningMap( return new NodePartitionMap(partitionToNode, bucketToPartition, getSplitToBucket(session, partitioningHandle)); } - private NodePartitionMap systemNodePartitionMap(Session session, PartitioningHandle partitioningHandle, AtomicReference> nodesCache) + private NodePartitionMap systemNodePartitionMap( + Session session, + PartitioningHandle partitioningHandle, + AtomicReference> nodesCache, + Optional partitionCount) { SystemPartitioning partitioning = ((SystemPartitioningHandle) partitioningHandle.getConnectorHandle()).getPartitioning(); @@ -211,7 +222,7 @@ private NodePartitionMap systemNodePartitionMap(Session session, PartitioningHan case FIXED -> { List value = nodesCache.get(); if (value == null) { - value = nodeSelector.selectRandomNodes(getHashPartitionCount(session)); + value = nodeSelector.selectRandomNodes(partitionCount.orElse(getMaxHashPartitionCount(session))); nodesCache.set(value); } yield value; @@ -239,15 +250,6 @@ public BucketNodeMap getBucketNodeMap(Session session, PartitioningHandle partit return new BucketNodeMap(splitToBucket, createArbitraryBucketToNode(nodes, bucketCount)); } - public int getBucketCount(Session session, PartitioningHandle partitioning) - { - if (partitioning.getConnectorHandle() instanceof MergePartitioningHandle) { - // TODO: can we always use this code path? - return getNodePartitioningMap(session, partitioning).getBucketToPartition().length; - } - return getBucketNodeMap(session, partitioning).getBucketCount(); - } - public int getNodeCount(Session session, PartitioningHandle partitioningHandle) { return getAllNodes(session, requiredCatalogHandle(partitioningHandle)).size(); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/OptimizerConfig.java b/core/trino-main/src/main/java/io/trino/sql/planner/OptimizerConfig.java index faf821a80da4..01339bb064d7 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/OptimizerConfig.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/OptimizerConfig.java @@ -23,6 +23,7 @@ import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; +import static io.airlift.units.DataSize.Unit.GIGABYTE; import static io.airlift.units.DataSize.Unit.MEGABYTE; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MINUTES; @@ -88,6 +89,8 @@ public class OptimizerConfig private long adaptivePartialAggregationMinRows = 100_000; private double adaptivePartialAggregationUniqueRowsRatioThreshold = 0.8; private long joinPartitionedBuildMinRowCount = 1_000_000L; + private DataSize minInputSizePerTask = DataSize.of(5, GIGABYTE); + private long minInputRowsPerTask = 10_000_000L; public enum JoinReorderingStrategy { @@ -744,6 +747,34 @@ public OptimizerConfig setJoinPartitionedBuildMinRowCount(long joinPartitionedBu return this; } + @NotNull + public DataSize getMinInputSizePerTask() + { + return minInputSizePerTask; + } + + @Config("optimizer.min-input-size-per-task") + @ConfigDescription("Minimum input data size required per task. This will help optimizer determine hash partition count for joins and aggregations") + public OptimizerConfig setMinInputSizePerTask(DataSize minInputSizePerTask) + { + this.minInputSizePerTask = minInputSizePerTask; + return this; + } + + @Min(0) + public long getMinInputRowsPerTask() + { + return minInputRowsPerTask; + } + + @Config("optimizer.min-input-rows-per-task") + @ConfigDescription("Minimum input rows required per task. This will help optimizer determine hash partition count for joins and aggregations") + public OptimizerConfig setMinInputRowsPerTask(long minInputRowsPerTask) + { + this.minInputRowsPerTask = minInputRowsPerTask; + return this; + } + public boolean isUseExactPartitioning() { return useExactPartitioning; diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/PartitioningScheme.java b/core/trino-main/src/main/java/io/trino/sql/planner/PartitioningScheme.java index fad8e028ab08..76c5d00124af 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/PartitioningScheme.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/PartitioningScheme.java @@ -34,6 +34,7 @@ public class PartitioningScheme private final Optional hashColumn; private final boolean replicateNullsAndAny; private final Optional bucketToPartition; + private final Optional partitionCount; public PartitioningScheme(Partitioning partitioning, List outputLayout) { @@ -42,6 +43,7 @@ public PartitioningScheme(Partitioning partitioning, List outputLayout) outputLayout, Optional.empty(), false, + Optional.empty(), Optional.empty()); } @@ -52,6 +54,7 @@ public PartitioningScheme(Partitioning partitioning, List outputLayout, outputLayout, hashColumn, false, + Optional.empty(), Optional.empty()); } @@ -61,7 +64,8 @@ public PartitioningScheme( @JsonProperty("outputLayout") List outputLayout, @JsonProperty("hashColumn") Optional hashColumn, @JsonProperty("replicateNullsAndAny") boolean replicateNullsAndAny, - @JsonProperty("bucketToPartition") Optional bucketToPartition) + @JsonProperty("bucketToPartition") Optional bucketToPartition, + @JsonProperty("partitionCount") Optional partitionCount) { this.partitioning = requireNonNull(partitioning, "partitioning is null"); this.outputLayout = ImmutableList.copyOf(requireNonNull(outputLayout, "outputLayout is null")); @@ -77,6 +81,10 @@ public PartitioningScheme( checkArgument(!replicateNullsAndAny || columns.size() <= 1, "Must have at most one partitioning column when nullPartition is REPLICATE."); this.replicateNullsAndAny = replicateNullsAndAny; this.bucketToPartition = requireNonNull(bucketToPartition, "bucketToPartition is null"); + this.partitionCount = requireNonNull(partitionCount, "partitionCount is null"); + checkArgument( + partitionCount.isEmpty() || partitioning.getHandle().getConnectorHandle() instanceof SystemPartitioningHandle, + "Connector partitioning handle should be of type system partitioning when partitionCount is present"); } @JsonProperty @@ -109,15 +117,26 @@ public Optional getBucketToPartition() return bucketToPartition; } + @JsonProperty + public Optional getPartitionCount() + { + return partitionCount; + } + public PartitioningScheme withBucketToPartition(Optional bucketToPartition) { - return new PartitioningScheme(partitioning, outputLayout, hashColumn, replicateNullsAndAny, bucketToPartition); + return new PartitioningScheme(partitioning, outputLayout, hashColumn, replicateNullsAndAny, bucketToPartition, partitionCount); } public PartitioningScheme withPartitioningHandle(PartitioningHandle partitioningHandle) { Partitioning newPartitioning = partitioning.withAlternativePartitioningHandle(partitioningHandle); - return new PartitioningScheme(newPartitioning, outputLayout, hashColumn, replicateNullsAndAny, bucketToPartition); + return new PartitioningScheme(newPartitioning, outputLayout, hashColumn, replicateNullsAndAny, bucketToPartition, partitionCount); + } + + public PartitioningScheme withPartitionCount(int partitionCount) + { + return new PartitioningScheme(partitioning, outputLayout, hashColumn, replicateNullsAndAny, bucketToPartition, Optional.of(partitionCount)); } public PartitioningScheme translateOutputLayout(List newOutputLayout) @@ -132,7 +151,7 @@ public PartitioningScheme translateOutputLayout(List newOutputLayout) .map(outputLayout::indexOf) .map(newOutputLayout::get); - return new PartitioningScheme(newPartitioning, newOutputLayout, newHashSymbol, replicateNullsAndAny, bucketToPartition); + return new PartitioningScheme(newPartitioning, newOutputLayout, newHashSymbol, replicateNullsAndAny, bucketToPartition, partitionCount); } @Override @@ -148,13 +167,14 @@ public boolean equals(Object o) return Objects.equals(partitioning, that.partitioning) && Objects.equals(outputLayout, that.outputLayout) && replicateNullsAndAny == that.replicateNullsAndAny && - Objects.equals(bucketToPartition, that.bucketToPartition); + Objects.equals(bucketToPartition, that.bucketToPartition) && + Objects.equals(partitionCount, that.partitionCount); } @Override public int hashCode() { - return Objects.hash(partitioning, outputLayout, replicateNullsAndAny, bucketToPartition); + return Objects.hash(partitioning, outputLayout, replicateNullsAndAny, bucketToPartition, partitionCount); } @Override @@ -166,6 +186,7 @@ public String toString() .add("hashChannel", hashColumn) .add("replicateNullsAndAny", replicateNullsAndAny) .add("bucketToPartition", bucketToPartition) + .add("partitionCount", partitionCount) .toString(); } } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/PlanFragmenter.java b/core/trino-main/src/main/java/io/trino/sql/planner/PlanFragmenter.java index b28cf94c3689..512a4ac4ae07 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/PlanFragmenter.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/PlanFragmenter.java @@ -188,7 +188,8 @@ private SubPlan reassignPartitioningHandleIfNecessaryHelper(Session session, Sub outputPartitioningScheme.getOutputLayout(), outputPartitioningScheme.getHashColumn(), outputPartitioningScheme.isReplicateNullsAndAny(), - outputPartitioningScheme.getBucketToPartition()), + outputPartitioningScheme.getBucketToPartition(), + outputPartitioningScheme.getPartitionCount()), fragment.getStatsAndCosts(), fragment.getActiveCatalogs(), fragment.getJsonRepresentation()); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java b/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java index b50ee5f15513..71e27a4b3a39 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java @@ -236,6 +236,8 @@ import io.trino.sql.planner.optimizations.AddLocalExchanges; import io.trino.sql.planner.optimizations.BeginTableWrite; import io.trino.sql.planner.optimizations.CheckSubqueryNodesAreRewritten; +import io.trino.sql.planner.optimizations.DeterminePartitionCount; +import io.trino.sql.planner.optimizations.DetermineWritersNodesCount; import io.trino.sql.planner.optimizations.HashGenerationOptimizer; import io.trino.sql.planner.optimizations.IndexJoinOptimizer; import io.trino.sql.planner.optimizations.LimitPushDown; @@ -841,6 +843,9 @@ public PlanOptimizers( // operators that require node partitioning builder.add(new UnaliasSymbolReferences(metadata)); builder.add(new StatsRecordingPlanOptimizer(optimizerStats, new AddExchanges(plannerContext, typeAnalyzer, statsCalculator))); + // It can only run after AddExchanges since it estimates the hash partition count for all remote exchanges + builder.add(new StatsRecordingPlanOptimizer(optimizerStats, new DeterminePartitionCount(statsCalculator))); + builder.add(new DetermineWritersNodesCount()); } // use cost calculator without estimated exchanges after AddExchanges @@ -859,7 +864,6 @@ public PlanOptimizers( .build())); // Run predicate push down one more time in case we can leverage new information from layouts' effective predicate - // and to pushdown dynamic filters builder.add(new StatsRecordingPlanOptimizer( optimizerStats, new PredicatePushDown(plannerContext, typeAnalyzer, true, false))); @@ -881,7 +885,7 @@ public PlanOptimizers( ImmutableSet.copyOf(new PushInequalityFilterExpressionBelowJoinRuleSet(metadata, typeAnalyzer).rules()))); // Projection pushdown rules may push reducing projections (e.g. dereferences) below filters for potential // pushdown into the connectors. Invoke PredicatePushdown and PushPredicateIntoTableScan after this - // to leverage predicate pushdown on projected columns. + // to leverage predicate pushdown on projected columns and to pushdown dynamic filters. builder.add(new StatsRecordingPlanOptimizer(optimizerStats, new PredicatePushDown(plannerContext, typeAnalyzer, true, true))); builder.add(new RemoveUnsupportedDynamicFilters(plannerContext)); // Remove unsupported dynamic filters introduced by PredicatePushdown builder.add(new IterativeOptimizer( diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/QueryPlanner.java b/core/trino-main/src/main/java/io/trino/sql/planner/QueryPlanner.java index b1fbf93b1e42..70bc48c193ff 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/QueryPlanner.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/QueryPlanner.java @@ -1033,7 +1033,7 @@ private PlanBuilder filter(PlanBuilder subPlan, Expression predicate, Node node) subPlan = subqueryPlanner.handleSubqueries(subPlan, predicate, analysis.getSubqueries(node)); - return subPlan.withNewRoot(new FilterNode(idAllocator.getNextId(), subPlan.getRoot(), subPlan.rewrite(predicate))); + return subPlan.withNewRoot(new FilterNode(idAllocator.getNextId(), subPlan.getRoot(), coerceIfNecessary(analysis, predicate, subPlan.rewrite(predicate)))); } private PlanBuilder aggregate(PlanBuilder subPlan, QuerySpecification node) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/RelationPlanner.java b/core/trino-main/src/main/java/io/trino/sql/planner/RelationPlanner.java index d3e7c56fb3dd..756841691a25 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/RelationPlanner.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/RelationPlanner.java @@ -65,6 +65,7 @@ import io.trino.sql.tree.Except; import io.trino.sql.tree.Expression; import io.trino.sql.tree.Identifier; +import io.trino.sql.tree.IfExpression; import io.trino.sql.tree.Intersect; import io.trino.sql.tree.Join; import io.trino.sql.tree.JoinCriteria; @@ -112,10 +113,13 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Iterables.getOnlyElement; +import static io.trino.spi.StandardErrorCode.CONSTRAINT_VIOLATION; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; +import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.sql.NodeUtils.getSortItemsFromOrderBy; import static io.trino.sql.analyzer.SemanticExceptions.semanticException; import static io.trino.sql.analyzer.TypeSignatureTranslator.toSqlType; +import static io.trino.sql.planner.LogicalPlanner.failFunction; import static io.trino.sql.planner.PlanBuilder.newPlanBuilder; import static io.trino.sql.planner.QueryPlanner.coerce; import static io.trino.sql.planner.QueryPlanner.coerceIfNecessary; @@ -279,7 +283,7 @@ public RelationPlan addRowFilters(Table node, RelationPlan plan, Function constraints, Table node, RelationPlan plan, Function accessControlScope) + { + if (constraints.isEmpty()) { + return plan; + } + + PlanBuilder planBuilder = newPlanBuilder(plan, analysis, lambdaDeclarationToSymbolMap, session, plannerContext) + .withScope(accessControlScope.apply(node), plan.getFieldMappings()); // The fields in the access control scope has the same layout as those for the table scope + + for (Expression constraint : constraints) { + planBuilder = subqueryPlanner.handleSubqueries(planBuilder, constraint, analysis.getSubqueries(constraint)); + + Expression predicate = new IfExpression( + // When predicate evaluates to UNKNOWN (e.g. NULL > 100), it should not violate the check constraint. + new CoalesceExpression(coerceIfNecessary(analysis, constraint, planBuilder.rewrite(constraint)), TRUE_LITERAL), + TRUE_LITERAL, + new Cast(failFunction(plannerContext.getMetadata(), session, CONSTRAINT_VIOLATION, "Check constraint violation: " + constraint), toSqlType(BOOLEAN))); + + planBuilder = planBuilder.withNewRoot(new FilterNode( + idAllocator.getNextId(), + planBuilder.getRoot(), + predicate)); + } + + return new RelationPlan(planBuilder.getRoot(), plan.getScope(), plan.getFieldMappings(), outerContext); + } + private RelationPlan addColumnMasks(Table table, RelationPlan plan) { Map columnMasks = analysis.getColumnMasks(table); @@ -809,7 +840,7 @@ else if (firstDependencies.stream().allMatch(right::canResolve) && secondDepende rootPlanBuilder = subqueryPlanner.handleSubqueries(rootPlanBuilder, complexJoinExpressions, subqueries); for (Expression expression : complexJoinExpressions) { - postInnerJoinConditions.add(rootPlanBuilder.rewrite(expression)); + postInnerJoinConditions.add(coerceIfNecessary(analysis, expression, rootPlanBuilder.rewrite(expression))); } root = rootPlanBuilder.getRoot(); @@ -994,7 +1025,7 @@ private RelationPlan planCorrelatedJoin(Join join, RelationPlan leftPlan, Latera .withAdditionalMappings(leftPlanBuilder.getTranslations().getMappings()) .withAdditionalMappings(rightPlanBuilder.getTranslations().getMappings()); - Expression rewrittenFilterCondition = translationMap.rewrite(filterExpression); + Expression rewrittenFilterCondition = coerceIfNecessary(analysis, filterExpression, translationMap.rewrite(filterExpression)); PlanBuilder planBuilder = subqueryPlanner.appendCorrelatedJoin( leftPlanBuilder, diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/AddExchangesBelowPartialAggregationOverGroupIdRuleSet.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/AddExchangesBelowPartialAggregationOverGroupIdRuleSet.java index a666f6daeec5..f0bbd4088452 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/AddExchangesBelowPartialAggregationOverGroupIdRuleSet.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/AddExchangesBelowPartialAggregationOverGroupIdRuleSet.java @@ -98,11 +98,12 @@ public class AddExchangesBelowPartialAggregationOverGroupIdRuleSet private static final Capture PROJECTION = newCapture(); private static final Capture AGGREGATION = newCapture(); private static final Capture GROUP_ID = newCapture(); + private static final Capture REMOTE_EXCHANGE = newCapture(); private static final Pattern WITH_PROJECTION = // If there was no exchange here, adding new exchanges could break property derivations logic of AddExchanges, AddLocalExchanges typeOf(ExchangeNode.class) - .with(scope().equalTo(REMOTE)) + .with(scope().equalTo(REMOTE)).capturedAs(REMOTE_EXCHANGE) .with(source().matching( // PushPartialAggregationThroughExchange adds a projection. However, it can be removed if RemoveRedundantIdentityProjections is run in the mean-time. typeOf(ProjectNode.class).capturedAs(PROJECTION) @@ -116,7 +117,7 @@ public class AddExchangesBelowPartialAggregationOverGroupIdRuleSet private static final Pattern WITHOUT_PROJECTION = // If there was no exchange here, adding new exchanges could break property derivations logic of AddExchanges, AddLocalExchanges typeOf(ExchangeNode.class) - .with(scope().equalTo(REMOTE)) + .with(scope().equalTo(REMOTE)).capturedAs(REMOTE_EXCHANGE) .with(source().matching( typeOf(AggregationNode.class).capturedAs(AGGREGATION) .with(step().equalTo(AggregationNode.Step.PARTIAL)) @@ -166,7 +167,8 @@ public Result apply(ExchangeNode exchange, Captures captures, Context context) ProjectNode project = captures.get(PROJECTION); AggregationNode aggregation = captures.get(AGGREGATION); GroupIdNode groupId = captures.get(GROUP_ID); - return transform(aggregation, groupId, context) + ExchangeNode remoteExchange = captures.get(REMOTE_EXCHANGE); + return transform(aggregation, groupId, remoteExchange.getPartitioningScheme().getPartitionCount(), context) .map(newAggregation -> Result.ofPlanNode( exchange.replaceChildren(ImmutableList.of( project.replaceChildren(ImmutableList.of( @@ -189,7 +191,8 @@ public Result apply(ExchangeNode exchange, Captures captures, Context context) { AggregationNode aggregation = captures.get(AGGREGATION); GroupIdNode groupId = captures.get(GROUP_ID); - return transform(aggregation, groupId, context) + ExchangeNode remoteExchange = captures.get(REMOTE_EXCHANGE); + return transform(aggregation, groupId, remoteExchange.getPartitioningScheme().getPartitionCount(), context) .map(newAggregation -> { PlanNode newExchange = exchange.replaceChildren(ImmutableList.of(newAggregation)); return Result.ofPlanNode(newExchange); @@ -212,7 +215,7 @@ public boolean isEnabled(Session session) return isEnableForcedExchangeBelowGroupId(session); } - protected Optional transform(AggregationNode aggregation, GroupIdNode groupId, Context context) + protected Optional transform(AggregationNode aggregation, GroupIdNode groupId, Optional partitionCount, Context context) { if (groupId.getGroupingSets().size() < 2) { return Optional.empty(); @@ -276,7 +279,12 @@ protected Optional transform(AggregationNode aggregation, GroupIdNode source, new PartitioningScheme( Partitioning.create(FIXED_HASH_DISTRIBUTION, desiredHashSymbols), - source.getOutputSymbols())); + source.getOutputSymbols(), + Optional.empty(), + false, + Optional.empty(), + // It's fine to reuse partitionCount since that is computed by considering all the expanding nodes and table scans in a query + partitionCount)); source = partitionedExchange( context.getIdAllocator().getNextId(), diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyPreferredTableExecutePartitioning.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyPreferredTableExecutePartitioning.java index 285929c303a8..e3105bac822b 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyPreferredTableExecutePartitioning.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyPreferredTableExecutePartitioning.java @@ -61,11 +61,6 @@ public Result apply(TableExecuteNode node, Captures captures, Context context) return enable(node); } int minimumNumberOfPartitions = getPreferredWritePartitioningMinNumberOfPartitions(context.getSession()); - if (minimumNumberOfPartitions <= 1) { - // Force 'preferred write partitioning' even if stats are missing or broken - return enable(node); - } - double expectedNumberOfPartitions = getRowsCount( context.getStatsProvider().getStats(node.getSource()), node.getPreferredPartitioningScheme().get().getPartitioning().getColumns()); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyPreferredTableWriterPartitioning.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyPreferredTableWriterPartitioning.java index 9745122240fe..e2147052880d 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyPreferredTableWriterPartitioning.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyPreferredTableWriterPartitioning.java @@ -67,11 +67,6 @@ public Result apply(TableWriterNode node, Captures captures, Context context) } int minimumNumberOfPartitions = getPreferredWritePartitioningMinNumberOfPartitions(context.getSession()); - if (minimumNumberOfPartitions <= 1) { - // Force 'preferred write partitioning' even if stats are missing or broken - return enable(node); - } - double expectedNumberOfPartitions = getRowsCount( context.getStatsProvider().getStats(node.getSource()), node.getPreferredPartitioningScheme().get().getPartitioning().getColumns()); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PruneExchangeColumns.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PruneExchangeColumns.java index fc6d1ca38f9f..2122ec834764 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PruneExchangeColumns.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PruneExchangeColumns.java @@ -105,7 +105,8 @@ protected Optional pushDownProjectOff(Context context, ExchangeNode ex newOutputs.build(), exchangeNode.getPartitioningScheme().getHashColumn(), exchangeNode.getPartitioningScheme().isReplicateNullsAndAny(), - exchangeNode.getPartitioningScheme().getBucketToPartition()); + exchangeNode.getPartitioningScheme().getBucketToPartition(), + exchangeNode.getPartitioningScheme().getPartitionCount()); return Optional.of(new ExchangeNode( exchangeNode.getId(), diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushPartialAggregationThroughExchange.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushPartialAggregationThroughExchange.java index 97c28495a32b..5fc335b8a6c5 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushPartialAggregationThroughExchange.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushPartialAggregationThroughExchange.java @@ -178,7 +178,8 @@ private PlanNode pushPartial(AggregationNode aggregation, ExchangeNode exchange, aggregation.getOutputSymbols(), exchange.getPartitioningScheme().getHashColumn(), exchange.getPartitioningScheme().isReplicateNullsAndAny(), - exchange.getPartitioningScheme().getBucketToPartition()); + exchange.getPartitioningScheme().getBucketToPartition(), + exchange.getPartitioningScheme().getPartitionCount()); return new ExchangeNode( context.getIdAllocator().getNextId(), diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushProjectionThroughExchange.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushProjectionThroughExchange.java index 26edf6900b8b..57d4ff82ea77 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushProjectionThroughExchange.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushProjectionThroughExchange.java @@ -172,7 +172,8 @@ public Result apply(ProjectNode project, Captures captures, Context context) outputBuilder.build(), exchange.getPartitioningScheme().getHashColumn(), exchange.getPartitioningScheme().isReplicateNullsAndAny(), - exchange.getPartitioningScheme().getBucketToPartition()); + exchange.getPartitioningScheme().getBucketToPartition(), + exchange.getPartitioningScheme().getPartitionCount()); PlanNode result = new ExchangeNode( exchange.getId(), diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushRemoteExchangeThroughAssignUniqueId.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushRemoteExchangeThroughAssignUniqueId.java index 64eb1a387672..44f5dcd887ff 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushRemoteExchangeThroughAssignUniqueId.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushRemoteExchangeThroughAssignUniqueId.java @@ -80,7 +80,8 @@ public Result apply(ExchangeNode node, Captures captures, Context context) removeSymbol(partitioningScheme.getOutputLayout(), assignUniqueId.getIdColumn()), partitioningScheme.getHashColumn(), partitioningScheme.isReplicateNullsAndAny(), - partitioningScheme.getBucketToPartition()), + partitioningScheme.getBucketToPartition(), + partitioningScheme.getPartitionCount()), ImmutableList.of(assignUniqueId.getSource()), ImmutableList.of(removeSymbol(getOnlyElement(node.getInputs()), assignUniqueId.getIdColumn())), Optional.empty()), diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/RemoveTrivialFilters.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/RemoveTrivialFilters.java index 1277f932c688..29e88d5f5820 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/RemoveTrivialFilters.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/RemoveTrivialFilters.java @@ -18,9 +18,11 @@ import io.trino.sql.planner.iterative.Rule; import io.trino.sql.planner.plan.FilterNode; import io.trino.sql.planner.plan.ValuesNode; +import io.trino.sql.tree.Cast; import io.trino.sql.tree.Expression; import io.trino.sql.tree.NullLiteral; +import static com.google.common.base.Preconditions.checkArgument; import static io.trino.sql.planner.plan.Patterns.filter; import static io.trino.sql.tree.BooleanLiteral.FALSE_LITERAL; import static io.trino.sql.tree.BooleanLiteral.TRUE_LITERAL; @@ -41,12 +43,14 @@ public Pattern getPattern() public Result apply(FilterNode filterNode, Captures captures, Context context) { Expression predicate = filterNode.getPredicate(); + checkArgument(!(predicate instanceof NullLiteral), "Unexpected null literal without a cast to boolean"); if (predicate.equals(TRUE_LITERAL)) { return Result.ofPlanNode(filterNode.getSource()); } - if (predicate.equals(FALSE_LITERAL) || predicate instanceof NullLiteral) { + if (predicate.equals(FALSE_LITERAL) || + (predicate instanceof Cast cast && cast.getExpression() instanceof NullLiteral)) { return Result.ofPlanNode(new ValuesNode(context.getIdAllocator().getNextId(), filterNode.getOutputSymbols(), emptyList())); } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/RewriteSpatialPartitioningAggregation.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/RewriteSpatialPartitioningAggregation.java index 0e64c07fdfd5..0bd95781c443 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/RewriteSpatialPartitioningAggregation.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/RewriteSpatialPartitioningAggregation.java @@ -38,7 +38,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.SystemSessionProperties.getFaultTolerantExecutionPartitionCount; -import static io.trino.SystemSessionProperties.getHashPartitionCount; +import static io.trino.SystemSessionProperties.getMaxHashPartitionCount; import static io.trino.SystemSessionProperties.getRetryPolicy; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypeSignatures; @@ -130,7 +130,7 @@ public Result apply(AggregationNode node, Captures captures, Context context) partitionCount = getFaultTolerantExecutionPartitionCount(context.getSession()); } else { - partitionCount = getHashPartitionCount(context.getSession()); + partitionCount = getMaxHashPartitionCount(context.getSession()); } return Result.ofPlanNode( AggregationNode.builderFrom(node) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/AddExchanges.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/AddExchanges.java index 667ab168b37c..5ac1f938156d 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/AddExchanges.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/AddExchanges.java @@ -975,6 +975,7 @@ public PlanWithProperties visitSemiJoin(SemiJoinNode node, PreferredProperties p filteringSource.getNode().getOutputSymbols(), Optional.empty(), true, + Optional.empty(), Optional.empty())), filteringSource.getProperties()); } @@ -1009,6 +1010,7 @@ public PlanWithProperties visitSemiJoin(SemiJoinNode node, PreferredProperties p filteringSource.getNode().getOutputSymbols(), Optional.empty(), true, + Optional.empty(), Optional.empty())), filteringSource.getProperties()); } @@ -1179,6 +1181,7 @@ public PlanWithProperties visitUnion(UnionNode node, PreferredProperties parentP source.getNode().getOutputSymbols(), Optional.empty(), nullsAndAnyReplicated, + Optional.empty(), Optional.empty())), source.getProperties()); } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DeterminePartitionCount.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DeterminePartitionCount.java new file mode 100644 index 000000000000..3aa4593cc2ca --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DeterminePartitionCount.java @@ -0,0 +1,321 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.sql.planner.optimizations; + +import com.google.common.collect.ImmutableList; +import io.airlift.log.Logger; +import io.trino.Session; +import io.trino.cost.CachingStatsProvider; +import io.trino.cost.StatsCalculator; +import io.trino.cost.StatsProvider; +import io.trino.cost.TableStatsProvider; +import io.trino.execution.warnings.WarningCollector; +import io.trino.operator.RetryPolicy; +import io.trino.sql.planner.PartitioningHandle; +import io.trino.sql.planner.PlanNodeIdAllocator; +import io.trino.sql.planner.SymbolAllocator; +import io.trino.sql.planner.SystemPartitioningHandle; +import io.trino.sql.planner.TypeProvider; +import io.trino.sql.planner.plan.ExchangeNode; +import io.trino.sql.planner.plan.JoinNode; +import io.trino.sql.planner.plan.MergeWriterNode; +import io.trino.sql.planner.plan.PlanNode; +import io.trino.sql.planner.plan.SimplePlanRewriter; +import io.trino.sql.planner.plan.TableExecuteNode; +import io.trino.sql.planner.plan.TableScanNode; +import io.trino.sql.planner.plan.TableWriterNode; +import io.trino.sql.planner.plan.UnionNode; +import io.trino.sql.planner.plan.UnnestNode; +import io.trino.sql.planner.plan.ValuesNode; + +import java.util.List; +import java.util.Optional; +import java.util.function.ToDoubleFunction; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.SystemSessionProperties.MAX_WRITERS_NODES_COUNT; +import static io.trino.SystemSessionProperties.getMaxHashPartitionCount; +import static io.trino.SystemSessionProperties.getMinHashPartitionCount; +import static io.trino.SystemSessionProperties.getMinInputRowsPerTask; +import static io.trino.SystemSessionProperties.getMinInputSizePerTask; +import static io.trino.SystemSessionProperties.getQueryMaxMemoryPerNode; +import static io.trino.SystemSessionProperties.getRetryPolicy; +import static io.trino.sql.planner.optimizations.QueryCardinalityUtil.isAtMostScalar; +import static io.trino.sql.planner.plan.ExchangeNode.Scope.REMOTE; +import static io.trino.sql.planner.plan.SimplePlanRewriter.rewriteWith; +import static java.lang.Double.isNaN; +import static java.lang.Math.incrementExact; +import static java.lang.Math.max; +import static java.util.Objects.requireNonNull; + +/** + * This rule looks at the amount of data read and processed by the query to determine the value of partition count + * used for remote exchanges. It helps to increase the concurrency of the engine in the case of large cluster. + * This rule is also cautious about lack of or incorrect statistics therefore it skips for input multiplying nodes like + * CROSS JOIN or UNNEST. + * + * E.g. 1: + * Given query: SELECT count(column_a) FROM table_with_stats_a + * config: + * MIN_INPUT_SIZE_PER_TASK: 500 MB + * Input table data size: 1000 MB + * Estimated partition count: Input table data size / MIN_INPUT_SIZE_PER_TASK => 2 + * + * E.g. 2: + * Given query: SELECT * FROM table_with_stats_a as a JOIN table_with_stats_b as b ON a.column_b = b.column_b + * config: + * MIN_INPUT_SIZE_PER_TASK: 500 MB + * Input tables data size: 1000 MB + * Join output data size: 5000 MB + * Estimated partition count: max((Input table data size / MIN_INPUT_SIZE_PER_TASK), (Join output data size / MIN_INPUT_SIZE_PER_TASK)) => 10 + */ +public class DeterminePartitionCount + implements PlanOptimizer +{ + private static final Logger log = Logger.get(DeterminePartitionCount.class); + private static final List> SKIP_PLAN_NODES = ImmutableList.of(TableExecuteNode.class, MergeWriterNode.class); + + private final StatsCalculator statsCalculator; + + public DeterminePartitionCount(StatsCalculator statsCalculator) + { + this.statsCalculator = requireNonNull(statsCalculator, "statsCalculator is null"); + } + + @Override + public PlanNode optimize( + PlanNode plan, + Session session, + TypeProvider types, + SymbolAllocator symbolAllocator, + PlanNodeIdAllocator idAllocator, + WarningCollector warningCollector, + TableStatsProvider tableStatsProvider) + { + requireNonNull(plan, "plan is null"); + requireNonNull(session, "session is null"); + requireNonNull(types, "types is null"); + requireNonNull(tableStatsProvider, "tableStatsProvider is null"); + + // Skip for write nodes since writing partitioned data with small amount of nodes could cause + // memory related issues even when the amount of data is small. Additionally, skip for FTE mode since we + // are not using estimated partitionCount in FTE scheduler. + if (PlanNodeSearcher.searchFrom(plan).whereIsInstanceOfAny(SKIP_PLAN_NODES).matches() + || getRetryPolicy(session) == RetryPolicy.TASK) { + return plan; + } + + List tableWriterSources = PlanNodeSearcher + .searchFrom(plan) + .recurseOnlyWhen(planNode -> planNode instanceof TableWriterNode) + .where(planNode -> planNode instanceof ExchangeNode) + .findAll(); + + Optional partitionCount = tableWriterSources.isEmpty() + ? determinePartitionCount(plan, session, types, tableStatsProvider) + : Optional.of(session.getSystemProperty(MAX_WRITERS_NODES_COUNT, Integer.class)); + try { + return partitionCount + .map(count -> rewriteWith(new Rewriter(count), plan)) + .orElse(plan); + } + + catch (RuntimeException e) { + log.warn(e, "Error occurred when determining hash partition count for query %s", session.getQueryId()); + } + + return plan; + } + + private Optional determinePartitionCount(PlanNode plan, Session session, TypeProvider types, TableStatsProvider tableStatsProvider) + { + long minInputSizePerTask = getMinInputSizePerTask(session).toBytes(); + long minInputRowsPerTask = getMinInputRowsPerTask(session); + if (minInputSizePerTask == 0 || minInputRowsPerTask == 0) { + return Optional.empty(); + } + + // Skip for expanding plan nodes like CROSS JOIN or UNNEST which can substantially increase the amount of data. + if (isInputMultiplyingPlanNodePresent(plan)) { + return Optional.empty(); + } + + StatsProvider statsProvider = new CachingStatsProvider(statsCalculator, session, types, tableStatsProvider); + long queryMaxMemoryPerNode = getQueryMaxMemoryPerNode(session).toBytes(); + + // Calculate partition count based on nodes output data size and rows + Optional partitionCountBasedOnOutputSize = getPartitionCountBasedOnOutputSize( + plan, + statsProvider, + types, + minInputSizePerTask, + queryMaxMemoryPerNode); + Optional partitionCountBasedOnRows = getPartitionCountBasedOnRows(plan, statsProvider, minInputRowsPerTask); + + if (partitionCountBasedOnOutputSize.isEmpty() || partitionCountBasedOnRows.isEmpty()) { + return Optional.empty(); + } + + int partitionCount = max( + // Consider both output size and rows count to estimate the value of partition count. This is essential + // because huge number of small size rows can be cpu intensive for some operators. On the other + // hand, small number of rows with considerable size in bytes can be memory intensive. + max(partitionCountBasedOnOutputSize.get(), partitionCountBasedOnRows.get()), + getMinHashPartitionCount(session)); + + int maxHashPartitionCount = getMaxHashPartitionCount(session); + if (partitionCount >= maxHashPartitionCount) { + return Optional.empty(); + } + + log.debug("Estimated remote exchange partition count for query %s is %s", session.getQueryId(), partitionCount); + return Optional.of(partitionCount); + } + + private static Optional getPartitionCountBasedOnOutputSize( + PlanNode plan, + StatsProvider statsProvider, + TypeProvider types, + long minInputSizePerTask, + long queryMaxMemoryPerNode) + { + double sourceTablesOutputSize = getSourceNodesOutputStats( + plan, + node -> statsProvider.getStats(node).getOutputSizeInBytes(node.getOutputSymbols(), types)); + double expandingNodesMaxOutputSize = getExpandingNodesMaxOutputStats( + plan, + node -> statsProvider.getStats(node).getOutputSizeInBytes(node.getOutputSymbols(), types)); + if (isNaN(sourceTablesOutputSize) || isNaN(expandingNodesMaxOutputSize)) { + return Optional.empty(); + } + int partitionCountBasedOnOutputSize = getPartitionCount( + max(sourceTablesOutputSize, expandingNodesMaxOutputSize), minInputSizePerTask); + + // Calculate partition count based on maximum memory usage. This is based on the assumption that + // generally operators won't keep data in memory more than the size of input data. + int partitionCountBasedOnMemory = (int) ((max(sourceTablesOutputSize, expandingNodesMaxOutputSize) * 2) / queryMaxMemoryPerNode); + + return Optional.of(max(partitionCountBasedOnOutputSize, partitionCountBasedOnMemory)); + } + + private static Optional getPartitionCountBasedOnRows(PlanNode plan, StatsProvider statsProvider, long minInputRowsPerTask) + { + double sourceTablesRowCount = getSourceNodesOutputStats(plan, node -> statsProvider.getStats(node).getOutputRowCount()); + double expandingNodesMaxRowCount = getExpandingNodesMaxOutputStats(plan, node -> statsProvider.getStats(node).getOutputRowCount()); + if (isNaN(sourceTablesRowCount) || isNaN(expandingNodesMaxRowCount)) { + return Optional.empty(); + } + + return Optional.of(getPartitionCount( + max(sourceTablesRowCount, expandingNodesMaxRowCount), minInputRowsPerTask)); + } + + private static int getPartitionCount(double outputStats, long minInputStatsPerTask) + { + return max((int) (outputStats / minInputStatsPerTask), 1); + } + + private static boolean isInputMultiplyingPlanNodePresent(PlanNode root) + { + return PlanNodeSearcher.searchFrom(root) + .where(DeterminePartitionCount::isInputMultiplyingPlanNode) + .matches(); + } + + private static boolean isInputMultiplyingPlanNode(PlanNode node) + { + if (node instanceof UnnestNode) { + return true; + } + + if (node instanceof JoinNode joinNode) { + // Skip for cross join + if (joinNode.isCrossJoin()) { + // If any of the input node is scalar then there's no need to skip cross join + return !isAtMostScalar(joinNode.getRight()) && !isAtMostScalar(joinNode.getLeft()); + } + + // Skip for joins with multi keys since output row count stats estimation can wrong due to + // low correlation between multiple join keys. + return joinNode.getCriteria().size() > 1; + } + + return false; + } + + private static double getExpandingNodesMaxOutputStats(PlanNode root, ToDoubleFunction statsMapper) + { + List expandingNodes = PlanNodeSearcher.searchFrom(root) + .where(DeterminePartitionCount::isExpandingPlanNode) + .findAll(); + + return expandingNodes.stream() + .mapToDouble(statsMapper) + .max() + .orElse(0); + } + + private static boolean isExpandingPlanNode(PlanNode node) + { + return node instanceof JoinNode + // consider union node and exchange node with multiple sources as expanding since it merge the rows + // from two different sources, thus more data is transferred over the network. + || node instanceof UnionNode + || (node instanceof ExchangeNode && node.getSources().size() > 1); + } + + private static double getSourceNodesOutputStats(PlanNode root, ToDoubleFunction statsMapper) + { + List sourceNodes = PlanNodeSearcher.searchFrom(root) + .whereIsInstanceOfAny(TableScanNode.class, ValuesNode.class) + .findAll(); + + return sourceNodes.stream() + .mapToDouble(statsMapper) + .sum(); + } + + private static class Rewriter + extends SimplePlanRewriter + { + private final int partitionCount; + + private Rewriter(int partitionCount) + { + this.partitionCount = partitionCount; + } + + @Override + public PlanNode visitExchange(ExchangeNode node, RewriteContext context) + { + PartitioningHandle handle = node.getPartitioningScheme().getPartitioning().getHandle(); + if (!(node.getScope() == REMOTE && handle.getConnectorHandle() instanceof SystemPartitioningHandle)) { + return node; + } + + List sources = node.getSources().stream() + .map(context::rewrite) + .collect(toImmutableList()); + + return new ExchangeNode( + node.getId(), + node.getType(), + node.getScope(), + node.getPartitioningScheme().withPartitionCount(partitionCount), + sources, + node.getInputs(), + node.getOrderingScheme()); + } + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DetermineWritersNodesCount.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DetermineWritersNodesCount.java new file mode 100644 index 000000000000..128b117527ff --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DetermineWritersNodesCount.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.sql.planner.optimizations; + +import io.trino.Session; +import io.trino.cost.TableStatsProvider; +import io.trino.execution.warnings.WarningCollector; +import io.trino.operator.RetryPolicy; +import io.trino.sql.planner.PartitioningHandle; +import io.trino.sql.planner.PlanNodeIdAllocator; +import io.trino.sql.planner.SymbolAllocator; +import io.trino.sql.planner.SystemPartitioningHandle; +import io.trino.sql.planner.TypeProvider; +import io.trino.sql.planner.plan.ExchangeNode; +import io.trino.sql.planner.plan.PlanNode; +import io.trino.sql.planner.plan.SimplePlanRewriter; +import io.trino.sql.planner.plan.TableWriterNode; + +import static io.trino.SystemSessionProperties.MAX_HASH_PARTITION_COUNT; +import static io.trino.SystemSessionProperties.MAX_WRITERS_NODES_COUNT; +import static io.trino.SystemSessionProperties.getRetryPolicy; +import static io.trino.sql.planner.SystemPartitioningHandle.SCALED_WRITER_HASH_DISTRIBUTION; +import static io.trino.sql.planner.plan.ExchangeNode.Scope.REMOTE; +import static io.trino.sql.planner.plan.SimplePlanRewriter.rewriteWith; +import static java.util.Objects.requireNonNull; + +public class DetermineWritersNodesCount + implements PlanOptimizer +{ + @Override + public PlanNode optimize( + PlanNode plan, + Session session, + TypeProvider types, + SymbolAllocator symbolAllocator, + PlanNodeIdAllocator idAllocator, + WarningCollector warningCollector, + TableStatsProvider tableStatsProvider) + { + requireNonNull(plan, "plan is null"); + requireNonNull(session, "session is null"); + + // Skip for plans where there is not writing stages Additionally, skip for FTE mode since we + // are not using estimated partitionCount in FTE scheduler. + + if (!PlanNodeSearcher.searchFrom(plan).whereIsInstanceOfAny(TableWriterNode.class).matches() || getRetryPolicy(session) == RetryPolicy.TASK) { + return plan; + } + + return rewriteWith(new Rewriter( + session.getSystemProperty(MAX_WRITERS_NODES_COUNT, Integer.class), + session.getSystemProperty(MAX_HASH_PARTITION_COUNT, Integer.class)), plan); + } + + private static class Rewriter + extends SimplePlanRewriter + { + private final int maxWriterNodesCount; + private final int maxHashPartitionCount; + + private Rewriter(int maxWriterNodesCount, int maxHashPartitionCount) + { + this.maxWriterNodesCount = maxWriterNodesCount; + this.maxHashPartitionCount = maxHashPartitionCount; + } + + @Override + public PlanNode visitTableWriter(TableWriterNode node, RewriteContext context) + { + if (!(node.getSource() instanceof ExchangeNode)) { + return node; + } + + ExchangeNode source = (ExchangeNode) node.getSource(); + PartitioningHandle handle = source.getPartitioningScheme().getPartitioning().getHandle(); + + if (source.getScope() != REMOTE || !(handle.getConnectorHandle() instanceof SystemPartitioningHandle) || handle != SCALED_WRITER_HASH_DISTRIBUTION) { + return node; + } + + // For TableWriterNode's sources (exchanges) there is no adaptive hash partition count. Then max-partition-hash-count is used. + // We limit that value with maxWriterNodesCount for writing stages, and only for them. + + return new TableWriterNode( + node.getId(), + new ExchangeNode( + source.getId(), + source.getType(), + source.getScope(), + source.getPartitioningScheme().withPartitionCount(Math.min(maxWriterNodesCount, maxHashPartitionCount)), + source.getSources(), + source.getInputs(), + source.getOrderingScheme()), + node.getTarget(), + node.getRowCountSymbol(), + node.getFragmentSymbol(), + node.getColumns(), + node.getColumnNames(), + node.getPartitioningScheme(), + node.getPreferredPartitioningScheme(), + node.getStatisticsAggregation(), + node.getStatisticsAggregationDescriptor()); + } + } +} diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/HashGenerationOptimizer.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/HashGenerationOptimizer.java index d1146bb91035..e4f9cadf04ec 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/HashGenerationOptimizer.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/HashGenerationOptimizer.java @@ -539,7 +539,8 @@ public PlanWithProperties visitExchange(ExchangeNode node, HashComputationSet pa .build(), partitionSymbols.map(newHashSymbols::get), partitioningScheme.isReplicateNullsAndAny(), - partitioningScheme.getBucketToPartition()); + partitioningScheme.getBucketToPartition(), + partitioningScheme.getPartitionCount()); // add hash symbols to sources ImmutableList.Builder> newInputs = ImmutableList.builder(); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/SymbolMapper.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/SymbolMapper.java index a8f379b57d2d..94f66ffb6daf 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/SymbolMapper.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/SymbolMapper.java @@ -482,7 +482,8 @@ public PartitioningScheme map(PartitioningScheme scheme, List sourceLayo mapAndDistinct(sourceLayout), scheme.getHashColumn().map(this::map), scheme.isReplicateNullsAndAny(), - scheme.getBucketToPartition()); + scheme.getBucketToPartition(), + scheme.getPartitionCount()); } public TableFinishNode map(TableFinishNode node, PlanNode source) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/plan/CorrelatedJoinNode.java b/core/trino-main/src/main/java/io/trino/sql/planner/plan/CorrelatedJoinNode.java index ed935944f519..60441272eacf 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/plan/CorrelatedJoinNode.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/plan/CorrelatedJoinNode.java @@ -20,6 +20,7 @@ import io.trino.sql.tree.Expression; import io.trino.sql.tree.Join; import io.trino.sql.tree.Node; +import io.trino.sql.tree.NullLiteral; import javax.annotation.concurrent.Immutable; @@ -100,6 +101,9 @@ public CorrelatedJoinNode( requireNonNull(subquery, "subquery is null"); requireNonNull(correlation, "correlation is null"); requireNonNull(filter, "filter is null"); + // The condition doesn't guarantee that filter is of type boolean, but was found to be a practical way to identify + // places where CorrelatedJoinNode could be created without appropriate coercions. + checkArgument(!(filter instanceof NullLiteral), "Filter must be an expression of boolean type: %s", filter); requireNonNull(originSubquery, "originSubquery is null"); checkArgument(input.getOutputSymbols().containsAll(correlation), "Input does not contain symbols from correlation"); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/plan/ExchangeNode.java b/core/trino-main/src/main/java/io/trino/sql/planner/plan/ExchangeNode.java index f5b8fe0fe3b2..52f7e10432b8 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/plan/ExchangeNode.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/plan/ExchangeNode.java @@ -132,6 +132,7 @@ public static ExchangeNode partitionedExchange(PlanNodeId id, Scope scope, PlanN child.getOutputSymbols(), hashColumns, replicateNullsAndAny, + Optional.empty(), Optional.empty())); } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/plan/FilterNode.java b/core/trino-main/src/main/java/io/trino/sql/planner/plan/FilterNode.java index a589aa5f5634..e7e8d935829d 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/plan/FilterNode.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/plan/FilterNode.java @@ -19,11 +19,15 @@ import com.google.common.collect.Iterables; import io.trino.sql.planner.Symbol; import io.trino.sql.tree.Expression; +import io.trino.sql.tree.NullLiteral; import javax.annotation.concurrent.Immutable; import java.util.List; +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + @Immutable public class FilterNode extends PlanNode @@ -39,6 +43,10 @@ public FilterNode(@JsonProperty("id") PlanNodeId id, super(id); this.source = source; + requireNonNull(predicate, "predicate is null"); + // The condition doesn't guarantee that predicate is of type boolean, but was found to be a practical way to identify + // places where FilterNode was created without appropriate coercions. + checkArgument(!(predicate instanceof NullLiteral), "Predicate must be an expression of boolean type: %s", predicate); this.predicate = predicate; } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/plan/JoinNode.java b/core/trino-main/src/main/java/io/trino/sql/planner/plan/JoinNode.java index 6b8c00f7f49c..e470e443d4e3 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/plan/JoinNode.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/plan/JoinNode.java @@ -23,6 +23,7 @@ import io.trino.sql.tree.ComparisonExpression; import io.trino.sql.tree.Expression; import io.trino.sql.tree.Join; +import io.trino.sql.tree.NullLiteral; import javax.annotation.concurrent.Immutable; @@ -89,6 +90,9 @@ public JoinNode( requireNonNull(leftOutputSymbols, "leftOutputSymbols is null"); requireNonNull(rightOutputSymbols, "rightOutputSymbols is null"); requireNonNull(filter, "filter is null"); + // The condition doesn't guarantee that filter is of type boolean, but was found to be a practical way to identify + // places where JoinNode could be created without appropriate coercions. + checkArgument(filter.isEmpty() || !(filter.get() instanceof NullLiteral), "Filter must be an expression of boolean type: %s", filter); requireNonNull(leftHashSymbol, "leftHashSymbol is null"); requireNonNull(rightHashSymbol, "rightHashSymbol is null"); requireNonNull(distributionType, "distributionType is null"); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/PlanPrinter.java b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/PlanPrinter.java index dcfa3fc2767d..1ab5824bbc65 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/PlanPrinter.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/PlanPrinter.java @@ -583,6 +583,8 @@ private static String formatFragment( hashColumn)); } + partitioningScheme.getPartitionCount().ifPresent(partitionCount -> builder.append(format("Partition count: %s\n", partitionCount))); + builder.append( new PlanPrinter( fragment.getRoot(), @@ -1639,6 +1641,7 @@ else if (node.getScope() == Scope.LOCAL) { addNode(node, format("%sExchange", UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, node.getScope().toString())), ImmutableMap.of( + "partitionCount", node.getPartitioningScheme().getPartitionCount().map(String::valueOf).orElse(""), "type", node.getType().name(), "isReplicateNullsAndAny", formatBoolean(node.getPartitioningScheme().isReplicateNullsAndAny()), "hashColumn", formatHash(node.getPartitioningScheme().getHashColumn())), diff --git a/core/trino-main/src/main/java/io/trino/testing/TestingMetadata.java b/core/trino-main/src/main/java/io/trino/testing/TestingMetadata.java index f4c74af43983..46320df3c19d 100644 --- a/core/trino-main/src/main/java/io/trino/testing/TestingMetadata.java +++ b/core/trino-main/src/main/java/io/trino/testing/TestingMetadata.java @@ -60,6 +60,8 @@ import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static io.trino.spi.StandardErrorCode.ALREADY_EXISTS; +import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.FRESH; +import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.STALE; import static java.util.Collections.synchronizedSet; import static java.util.Objects.requireNonNull; @@ -269,7 +271,7 @@ public void dropMaterializedView(ConnectorSession session, SchemaTableName viewN @Override public MaterializedViewFreshness getMaterializedViewFreshness(ConnectorSession session, SchemaTableName name) { - return new MaterializedViewFreshness(freshMaterializedViews.contains(name)); + return new MaterializedViewFreshness(freshMaterializedViews.contains(name) ? FRESH : STALE); } public void markMaterializedViewIsFresh(SchemaTableName name) diff --git a/core/trino-main/src/main/java/io/trino/type/TypeCoercion.java b/core/trino-main/src/main/java/io/trino/type/TypeCoercion.java index 4be3958939d7..b1455c76d18a 100644 --- a/core/trino-main/src/main/java/io/trino/type/TypeCoercion.java +++ b/core/trino-main/src/main/java/io/trino/type/TypeCoercion.java @@ -46,10 +46,8 @@ import static io.trino.spi.type.RowType.Field; import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimeType.createTimeType; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; import static io.trino.spi.type.TimeWithTimeZoneType.createTimeWithTimeZoneType; import static io.trino.spi.type.TimestampType.createTimestampType; -import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.createTimestampWithTimeZoneType; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.spi.type.VarcharType.createVarcharType; @@ -453,7 +451,7 @@ public Optional coerceTypeBase(Type sourceType, String resultTypeBase) case StandardTypes.TIMESTAMP: return Optional.of(createTimestampType(0)); case StandardTypes.TIMESTAMP_WITH_TIME_ZONE: - return Optional.of(TIMESTAMP_TZ_MILLIS); + return Optional.of(createTimestampWithTimeZoneType(0)); default: return Optional.empty(); } @@ -461,7 +459,7 @@ public Optional coerceTypeBase(Type sourceType, String resultTypeBase) case StandardTypes.TIME: { switch (resultTypeBase) { case StandardTypes.TIME_WITH_TIME_ZONE: - return Optional.of(TIME_WITH_TIME_ZONE); + return Optional.of(createTimeWithTimeZoneType(((TimeType) sourceType).getPrecision())); default: return Optional.empty(); } diff --git a/core/trino-main/src/test/java/io/trino/block/TestRowBlock.java b/core/trino-main/src/test/java/io/trino/block/TestRowBlock.java index 224e3eef9a7a..bdc07dd4d62d 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestRowBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestRowBlock.java @@ -108,8 +108,8 @@ public void testCompactBlock() Block emptyBlock = new ByteArrayBlock(0, Optional.empty(), new byte[0]); Block compactFieldBlock1 = new ByteArrayBlock(5, Optional.empty(), createExpectedValue(5).getBytes()); Block compactFieldBlock2 = new ByteArrayBlock(5, Optional.empty(), createExpectedValue(5).getBytes()); - Block incompactFiledBlock1 = new ByteArrayBlock(5, Optional.empty(), createExpectedValue(6).getBytes()); - Block incompactFiledBlock2 = new ByteArrayBlock(5, Optional.empty(), createExpectedValue(6).getBytes()); + Block incompactFieldBlock1 = new ByteArrayBlock(5, Optional.empty(), createExpectedValue(6).getBytes()); + Block incompactFieldBlock2 = new ByteArrayBlock(5, Optional.empty(), createExpectedValue(6).getBytes()); boolean[] rowIsNull = {false, true, false, false, false, false}; assertCompact(fromFieldBlocks(0, Optional.empty(), new Block[] {emptyBlock, emptyBlock})); @@ -117,8 +117,8 @@ public void testCompactBlock() // TODO: add test case for a sliced RowBlock // underlying field blocks are not compact - testIncompactBlock(fromFieldBlocks(rowIsNull.length, Optional.of(rowIsNull), new Block[] {incompactFiledBlock1, incompactFiledBlock2})); - testIncompactBlock(fromFieldBlocks(rowIsNull.length, Optional.of(rowIsNull), new Block[] {incompactFiledBlock1, incompactFiledBlock2})); + testIncompactBlock(fromFieldBlocks(rowIsNull.length, Optional.of(rowIsNull), new Block[] {incompactFieldBlock1, incompactFieldBlock2})); + testIncompactBlock(fromFieldBlocks(rowIsNull.length, Optional.of(rowIsNull), new Block[] {incompactFieldBlock1, incompactFieldBlock2})); } private void testWith(List fieldTypes, List[] expectedValues) 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 c7172aa870a7..4c6f7b0d6194 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 @@ -113,6 +113,8 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.connector.MockConnector.MockConnectorSplit.MOCK_CONNECTOR_SPLIT; +import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.FRESH; +import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.STALE; import static io.trino.spi.connector.RowChangeParadigm.DELETE_ROW_AND_INSERT_ROW; import static io.trino.spi.type.BigintType.BIGINT; import static java.util.Objects.requireNonNull; @@ -136,6 +138,7 @@ public class MockConnector private final BiFunction getTableHandle; private final Function> getColumns; private final Function getTableStatistics; + private final Function> checkConstraints; private final MockConnectorFactory.ApplyProjection applyProjection; private final MockConnectorFactory.ApplyAggregation applyAggregation; private final MockConnectorFactory.ApplyJoin applyJoin; @@ -177,6 +180,7 @@ public class MockConnector BiFunction getTableHandle, Function> getColumns, Function getTableStatistics, + Function> checkConstraints, ApplyProjection applyProjection, ApplyAggregation applyAggregation, ApplyJoin applyJoin, @@ -216,6 +220,7 @@ public class MockConnector this.getTableHandle = requireNonNull(getTableHandle, "getTableHandle is null"); this.getColumns = requireNonNull(getColumns, "getColumns is null"); this.getTableStatistics = requireNonNull(getTableStatistics, "getTableStatistics is null"); + this.checkConstraints = requireNonNull(checkConstraints, "checkConstraints is null"); this.applyProjection = requireNonNull(applyProjection, "applyProjection is null"); this.applyAggregation = requireNonNull(applyAggregation, "applyAggregation is null"); this.applyJoin = requireNonNull(applyJoin, "applyJoin is null"); @@ -465,7 +470,12 @@ public ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTable public ConnectorTableMetadata getTableMetadata(ConnectorSession session, ConnectorTableHandle tableHandle) { MockConnectorTableHandle table = (MockConnectorTableHandle) tableHandle; - return new ConnectorTableMetadata(table.getTableName(), getColumns.apply(table.getTableName())); + return new ConnectorTableMetadata( + table.getTableName(), + getColumns.apply(table.getTableName()), + ImmutableMap.of(), + Optional.empty(), + checkConstraints.apply(table.getTableName())); } @Override @@ -592,7 +602,7 @@ public MaterializedViewFreshness getMaterializedViewFreshness(ConnectorSession s { ConnectorMaterializedViewDefinition view = getMaterializedViews.apply(session, viewName.toSchemaTablePrefix()).get(viewName); checkArgument(view != null, "Materialized view %s does not exist", viewName); - return new MaterializedViewFreshness(view.getStorageTable().isPresent()); + return new MaterializedViewFreshness(view.getStorageTable().isPresent() ? FRESH : STALE); } @Override diff --git a/core/trino-main/src/test/java/io/trino/connector/MockConnectorFactory.java b/core/trino-main/src/test/java/io/trino/connector/MockConnectorFactory.java index 8f6eaebdab52..c722c0f28fb3 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 @@ -93,6 +93,7 @@ public class MockConnectorFactory private final BiFunction getTableHandle; private final Function> getColumns; private final Function getTableStatistics; + private final Function> checkConstraints; private final ApplyProjection applyProjection; private final ApplyAggregation applyAggregation; private final ApplyJoin applyJoin; @@ -136,6 +137,7 @@ private MockConnectorFactory( BiFunction getTableHandle, Function> getColumns, Function getTableStatistics, + Function> checkConstraints, ApplyProjection applyProjection, ApplyAggregation applyAggregation, ApplyJoin applyJoin, @@ -176,6 +178,7 @@ private MockConnectorFactory( this.getTableHandle = requireNonNull(getTableHandle, "getTableHandle is null"); this.getColumns = requireNonNull(getColumns, "getColumns is null"); this.getTableStatistics = requireNonNull(getTableStatistics, "getTableStatistics is null"); + this.checkConstraints = requireNonNull(checkConstraints, "checkConstraints is null"); this.applyProjection = requireNonNull(applyProjection, "applyProjection is null"); this.applyAggregation = requireNonNull(applyAggregation, "applyAggregation is null"); this.applyJoin = requireNonNull(applyJoin, "applyJoin is null"); @@ -226,6 +229,7 @@ public Connector create(String catalogName, Map config, Connecto getTableHandle, getColumns, getTableStatistics, + checkConstraints, applyProjection, applyAggregation, applyJoin, @@ -353,6 +357,7 @@ public static final class Builder private BiFunction getTableHandle = defaultGetTableHandle(); private Function> getColumns = defaultGetColumns(); private Function getTableStatistics = schemaTableName -> empty(); + private Function> checkConstraints = (schemaTableName -> ImmutableList.of()); private ApplyProjection applyProjection = (session, handle, projections, assignments) -> Optional.empty(); private ApplyAggregation applyAggregation = (session, handle, aggregates, assignments, groupingSets) -> Optional.empty(); private ApplyJoin applyJoin = (session, joinType, left, right, joinConditions, leftAssignments, rightAssignments) -> Optional.empty(); @@ -474,6 +479,12 @@ public Builder withGetTableStatistics(Function return this; } + public Builder withCheckConstraints(Function> checkConstraints) + { + this.checkConstraints = requireNonNull(checkConstraints, "checkConstraints is null"); + return this; + } + public Builder withApplyProjection(ApplyProjection applyProjection) { this.applyProjection = applyProjection; @@ -683,6 +694,7 @@ public MockConnectorFactory build() getTableHandle, getColumns, getTableStatistics, + checkConstraints, applyProjection, applyAggregation, applyJoin, diff --git a/core/trino-main/src/test/java/io/trino/cost/TestOptimizerConfig.java b/core/trino-main/src/test/java/io/trino/cost/TestOptimizerConfig.java index 12306998472d..ac45a1c66475 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestOptimizerConfig.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestOptimizerConfig.java @@ -89,6 +89,8 @@ public void testDefaults() .setAdaptivePartialAggregationMinRows(100_000) .setAdaptivePartialAggregationUniqueRowsRatioThreshold(0.8) .setJoinPartitionedBuildMinRowCount(1_000_000) + .setMinInputSizePerTask(DataSize.of(5, GIGABYTE)) + .setMinInputRowsPerTask(10_000_000L) .setUseExactPartitioning(false)); } @@ -146,6 +148,8 @@ public void testExplicitPropertyMappings() .put("adaptive-partial-aggregation.min-rows", "1") .put("adaptive-partial-aggregation.unique-rows-ratio-threshold", "0.99") .put("optimizer.join-partitioned-build-min-row-count", "1") + .put("optimizer.min-input-size-per-task", "1MB") + .put("optimizer.min-input-rows-per-task", "1000000") .put("optimizer.use-exact-partitioning", "true") .buildOrThrow(); @@ -200,6 +204,8 @@ public void testExplicitPropertyMappings() .setAdaptivePartialAggregationMinRows(1) .setAdaptivePartialAggregationUniqueRowsRatioThreshold(0.99) .setJoinPartitionedBuildMinRowCount(1) + .setMinInputSizePerTask(DataSize.of(1, MEGABYTE)) + .setMinInputRowsPerTask(1_000_000L) .setUseExactPartitioning(true); assertFullMapping(properties, expected); } diff --git a/core/trino-main/src/test/java/io/trino/execution/MockRemoteTaskFactory.java b/core/trino-main/src/test/java/io/trino/execution/MockRemoteTaskFactory.java index 2d798ce4d0b9..4ef6ccb09dbc 100644 --- a/core/trino-main/src/test/java/io/trino/execution/MockRemoteTaskFactory.java +++ b/core/trino-main/src/test/java/io/trino/execution/MockRemoteTaskFactory.java @@ -257,6 +257,7 @@ public TaskInfo getTaskInfo() outputBuffer.getStatus(), DataSize.ofBytes(0), DataSize.ofBytes(0), + Optional.empty(), DataSize.ofBytes(0), DataSize.ofBytes(0), DataSize.ofBytes(0), @@ -291,6 +292,7 @@ public TaskStatus getTaskStatus() outputBuffer.getStatus(), stats.getOutputDataSize(), stats.getPhysicalWrittenDataSize(), + stats.getMaxWriterCount(), stats.getUserMemoryReservation(), stats.getPeakUserMemoryReservation(), stats.getRevocableMemoryReservation(), diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java index fa5c0ae42485..bf073a6feb3c 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java @@ -117,7 +117,7 @@ public class TestCreateMaterializedViewTask private AnalyzerFactory analyzerFactory; private MaterializedViewPropertyManager materializedViewPropertyManager; private LocalQueryRunner queryRunner; - private CatalogHandle testCatlogHandle; + private CatalogHandle testCatalogHandle; @BeforeMethod public void setUp() @@ -136,7 +136,7 @@ public void setUp() .build()) .build(), ImmutableMap.of()); - testCatlogHandle = queryRunner.getCatalogHandle(TEST_CATALOG_NAME); + testCatalogHandle = queryRunner.getCatalogHandle(TEST_CATALOG_NAME); materializedViewPropertyManager = queryRunner.getMaterializedViewPropertyManager(); @@ -320,7 +320,7 @@ public void createMaterializedView(Session session, QualifiedObjectName viewName public Optional getCatalogHandle(Session session, String catalogName) { if (TEST_CATALOG_NAME.equals(catalogName)) { - return Optional.of(testCatlogHandle); + return Optional.of(testCatalogHandle); } return Optional.empty(); } @@ -337,7 +337,7 @@ public Optional getTableHandle(Session session, QualifiedObjectName if (tableName.asSchemaTableName().equals(MOCK_TABLE.getTable())) { return Optional.of( new TableHandle( - testCatlogHandle, + testCatalogHandle, new TestingTableHandle(tableName.asSchemaTableName()), TestingConnectorTransactionHandle.INSTANCE)); } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java b/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java index d0d2d5c663b9..5044d053ea6a 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java @@ -49,7 +49,8 @@ public void testDefaults() .setMinScheduleSplitBatchSize(100) .setMaxConcurrentQueries(1000) .setMaxQueuedQueries(5000) - .setHashPartitionCount(100) + .setMaxHashPartitionCount(100) + .setMinHashPartitionCount(4) .setQueryManagerExecutorPoolSize(5) .setQueryExecutorPoolSize(1000) .setRemoteTaskMinErrorDuration(new Duration(5, MINUTES)) @@ -96,7 +97,8 @@ public void testExplicitPropertyMappings() .put("query.min-schedule-split-batch-size", "9") .put("query.max-concurrent-queries", "10") .put("query.max-queued-queries", "15") - .put("query.hash-partition-count", "16") + .put("query.max-hash-partition-count", "16") + .put("query.min-hash-partition-count", "2") .put("query.manager-executor-pool-size", "11") .put("query.executor-pool-size", "111") .put("query.remote-task.min-error-duration", "30s") @@ -140,7 +142,8 @@ public void testExplicitPropertyMappings() .setMinScheduleSplitBatchSize(9) .setMaxConcurrentQueries(10) .setMaxQueuedQueries(15) - .setHashPartitionCount(16) + .setMaxHashPartitionCount(16) + .setMinHashPartitionCount(2) .setQueryManagerExecutorPoolSize(11) .setQueryExecutorPoolSize(111) .setRemoteTaskMinErrorDuration(new Duration(60, SECONDS)) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestTaskManagerConfig.java b/core/trino-main/src/test/java/io/trino/execution/TestTaskManagerConfig.java index e233b0ca0d10..7a04e0165390 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestTaskManagerConfig.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestTaskManagerConfig.java @@ -34,6 +34,7 @@ public class TestTaskManagerConfig { private static final int DEFAULT_PROCESSOR_COUNT = min(max(nextPowerOfTwo(getAvailablePhysicalProcessorCount()), 2), 32); + private static final int DEFAULT_SCALE_WRITERS_MAX_WRITER_COUNT = min(getAvailablePhysicalProcessorCount(), 32); @Test public void testDefaults() @@ -59,8 +60,9 @@ public void testDefaults() .setSinkMaxBufferSize(DataSize.of(32, Unit.MEGABYTE)) .setSinkMaxBroadcastBufferSize(DataSize.of(200, Unit.MEGABYTE)) .setMaxPagePartitioningBufferSize(DataSize.of(32, Unit.MEGABYTE)) + .setPagePartitioningBufferPoolSize(8) .setScaleWritersEnabled(true) - .setScaleWritersMaxWriterCount(8) + .setScaleWritersMaxWriterCount(DEFAULT_SCALE_WRITERS_MAX_WRITER_COUNT) .setWriterCount(1) .setPartitionedWriterCount(DEFAULT_PROCESSOR_COUNT) .setTaskConcurrency(DEFAULT_PROCESSOR_COUNT) @@ -80,6 +82,7 @@ public void testDefaults() public void testExplicitPropertyMappings() { int processorCount = DEFAULT_PROCESSOR_COUNT == 32 ? 16 : 32; + int maxWriterCount = DEFAULT_SCALE_WRITERS_MAX_WRITER_COUNT == 32 ? 16 : 32; Map properties = ImmutableMap.builder() .put("task.initial-splits-per-node", "1") .put("task.split-concurrency-adjustment-interval", "1s") @@ -101,8 +104,9 @@ public void testExplicitPropertyMappings() .put("sink.max-buffer-size", "42MB") .put("sink.max-broadcast-buffer-size", "128MB") .put("driver.max-page-partitioning-buffer-size", "40MB") + .put("driver.page-partitioning-buffer-pool-size", "0") .put("task.scale-writers.enabled", "false") - .put("task.scale-writers.max-writer-count", "4") + .put("task.scale-writers.max-writer-count", Integer.toString(maxWriterCount)) .put("task.writer-count", "4") .put("task.partitioned-writer-count", Integer.toString(processorCount)) .put("task.concurrency", Integer.toString(processorCount)) @@ -139,8 +143,9 @@ public void testExplicitPropertyMappings() .setSinkMaxBufferSize(DataSize.of(42, Unit.MEGABYTE)) .setSinkMaxBroadcastBufferSize(DataSize.of(128, Unit.MEGABYTE)) .setMaxPagePartitioningBufferSize(DataSize.of(40, Unit.MEGABYTE)) + .setPagePartitioningBufferPoolSize(0) .setScaleWritersEnabled(false) - .setScaleWritersMaxWriterCount(4) + .setScaleWritersMaxWriterCount(maxWriterCount) .setWriterCount(4) .setPartitionedWriterCount(processorCount) .setTaskConcurrency(processorCount) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestingRemoteTaskFactory.java b/core/trino-main/src/test/java/io/trino/execution/TestingRemoteTaskFactory.java index 3700d0b4d79c..769b7ea0f34e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestingRemoteTaskFactory.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestingRemoteTaskFactory.java @@ -180,6 +180,7 @@ public TaskStatus getTaskStatus() OutputBufferStatus.initial(), DataSize.of(0, BYTE), DataSize.of(0, BYTE), + Optional.empty(), DataSize.of(0, BYTE), DataSize.of(0, BYTE), DataSize.of(0, BYTE), diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java index 77a7085215b2..59de15c34c63 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java @@ -200,14 +200,14 @@ public void testVarcharSerializedSize() // empty page Page page = new Page(builder.build()); int pageSize = serializedSize(ImmutableList.of(VARCHAR), page); - assertEquals(pageSize, 44); + assertEquals(pageSize, 48); // page with one value VARCHAR.writeString(builder, "alice"); pageSize = 44; // Now we have moved to the normal block implementation so the page size overhead is 44 page = new Page(builder.build()); int firstValueSize = serializedSize(ImmutableList.of(VARCHAR), page) - pageSize; - assertEquals(firstValueSize, 4 + 5); // length + "alice" + assertEquals(firstValueSize, 8 + 5); // length + nonNullsCount + "alice" // page with two values VARCHAR.writeString(builder, "bob"); diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledWriterScheduler.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledWriterScheduler.java index 4ba83f907c02..a2d11ae06842 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledWriterScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledWriterScheduler.java @@ -47,6 +47,7 @@ import java.util.List; import java.util.Optional; import java.util.OptionalLong; +import java.util.concurrent.atomic.AtomicReference; import static io.airlift.concurrent.Threads.threadsNamed; import static io.trino.execution.TestingRemoteTaskFactory.TestingRemoteTask; @@ -64,18 +65,16 @@ public class TestScaledWriterScheduler private static final PlanNodeId TABLE_SCAN_NODE_ID = new PlanNodeId("plan_id"); private static final InternalNode NODE_1 = new InternalNode("node-1", URI.create("http://127.0.0.1:11"), new NodeVersion("version-1"), false); private static final InternalNode NODE_2 = new InternalNode("node-2", URI.create("http://127.0.0.1:12"), new NodeVersion("version-1"), false); + private static final InternalNode NODE_3 = new InternalNode("node-3", URI.create("http://127.0.0.1:13"), new NodeVersion("version-1"), false); @Test public void testGetNewTaskCountWithUnderutilizedTasksWithoutSkewness() { - TaskStatus taskStatus1 = buildTaskStatus(false, 12345L); + TaskStatus taskStatus1 = buildTaskStatus(true, 12345L); TaskStatus taskStatus2 = buildTaskStatus(false, 12345L); - TaskStatus taskStatus3 = buildTaskStatus(true, 12345L); - - ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatus1, taskStatus2, taskStatus3); - - scaledWriterScheduler.schedule(); + TaskStatus taskStatus3 = buildTaskStatus(false, 12345L); + ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 0); } @@ -86,24 +85,18 @@ public void testGetNewTaskCountWithOverutilizedTasksWithoutSkewness() TaskStatus taskStatus2 = buildTaskStatus(true, 12345L); TaskStatus taskStatus3 = buildTaskStatus(false, 12345L); - ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatus1, taskStatus2, taskStatus3); - - scaledWriterScheduler.schedule(); - + ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); } @Test public void testGetNewTaskCountWithOverutilizedSkewedTaskAndUnderutilizedNonSkewedTasks() { - TaskStatus taskStatus1 = buildTaskStatus(false, 12345L); - TaskStatus taskStatus2 = buildTaskStatus(false, 123456L); - TaskStatus taskStatus3 = buildTaskStatus(true, 1234567L); - - ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatus1, taskStatus2, taskStatus3); - - scaledWriterScheduler.schedule(); + TaskStatus taskStatus1 = buildTaskStatus(true, 1234567L); + TaskStatus taskStatus2 = buildTaskStatus(false, 12345L); + TaskStatus taskStatus3 = buildTaskStatus(false, 123456L); + ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); } @@ -114,29 +107,112 @@ public void testGetNewTaskCountWithUnderutilizedSkewedTaskAndOverutilizedNonSkew TaskStatus taskStatus2 = buildTaskStatus(true, 123456L); TaskStatus taskStatus3 = buildTaskStatus(false, 1234567L); - ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatus1, taskStatus2, taskStatus3); + ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); + assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + } + + @Test + public void testGetNewTaskCountWhenWrittenBytesIsGreaterThanMinWrittenBytesForScaleUp() + { + TaskStatus taskStatus1 = buildTaskStatus(1, DataSize.of(32, DataSize.Unit.MEGABYTE)); + TaskStatus taskStatus2 = buildTaskStatus(1, DataSize.of(32, DataSize.Unit.MEGABYTE)); + TaskStatus taskStatus3 = buildTaskStatus(2, DataSize.of(64, DataSize.Unit.MEGABYTE)); + + ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); + // Scale up will happen + assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + } + + @Test + public void testGetNewTaskCountWhenWrittenBytesIsLessThanMinWrittenBytesForScaleUp() + { + TaskStatus taskStatus1 = buildTaskStatus(1, DataSize.of(32, DataSize.Unit.MEGABYTE)); + TaskStatus taskStatus2 = buildTaskStatus(1, DataSize.of(32, DataSize.Unit.MEGABYTE)); + TaskStatus taskStatus3 = buildTaskStatus(2, DataSize.of(32, DataSize.Unit.MEGABYTE)); + + ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); + // Scale up will not happen because for one of the task there are two local writers which makes the + // minWrittenBytes for scaling up to (2 * writerMinSizeBytes) that is greater than physicalWrittenBytes. + assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 0); + } + + @Test + public void testGetNewTaskCountWhenExistingWriterTaskMaxWriterCountIsEmpty() + { + TaskStatus taskStatus1 = buildTaskStatus(1, DataSize.of(32, DataSize.Unit.MEGABYTE)); + TaskStatus taskStatus2 = buildTaskStatus(2, DataSize.of(100, DataSize.Unit.MEGABYTE)); + TaskStatus taskStatus3 = buildTaskStatus(true, 12345L, Optional.empty(), DataSize.of(0, DataSize.Unit.MEGABYTE)); + + ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); + // Scale up will not happen because one of the existing writer task isn't initialized yet with maxWriterCount. + assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 0); + } + + @Test + public void testNewTaskCountWhenUpperLimitIsNotExceeded() + { + TaskStatus taskStatus = buildTaskStatus(true, 123456L); + AtomicReference> taskStatusProvider = new AtomicReference<>(ImmutableList.of(taskStatus)); + ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatusProvider, 2); + + scaledWriterScheduler.schedule(); + assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + } + + @Test + public void testNewTaskCountWhenNodesUpperLimitIsExceeded() + { + TaskStatus taskStatus = buildTaskStatus(true, 123456L); + AtomicReference> taskStatusProvider = new AtomicReference<>(ImmutableList.of(taskStatus)); + ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatusProvider, 1); scaledWriterScheduler.schedule(); + assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 0); + } + + private ScaledWriterScheduler buildScaleWriterSchedulerWithInitialTasks(TaskStatus taskStatus1, TaskStatus taskStatus2, TaskStatus taskStatus3) + { + AtomicReference> taskStatusProvider = new AtomicReference<>(ImmutableList.of()); + ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatusProvider, 100); + + assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + taskStatusProvider.set(ImmutableList.of(taskStatus1)); + + assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + taskStatusProvider.set(ImmutableList.of(taskStatus1, taskStatus2)); assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + taskStatusProvider.set(ImmutableList.of(taskStatus1, taskStatus2, taskStatus3)); + + return scaledWriterScheduler; } - private ScaledWriterScheduler buildScaledWriterScheduler(TaskStatus taskStatus1, TaskStatus taskStatus2, TaskStatus taskStatus3) + private ScaledWriterScheduler buildScaledWriterScheduler(AtomicReference> taskStatusProvider, int maxWritersNodesCount) { return new ScaledWriterScheduler( new TestingStageExecution(createFragment()), - () -> ImmutableList.of(taskStatus1, taskStatus2, taskStatus3), - () -> ImmutableList.of(taskStatus1, taskStatus2, taskStatus3), + taskStatusProvider::get, + taskStatusProvider::get, new UniformNodeSelectorFactory( - new InMemoryNodeManager(NODE_1, NODE_2), + new InMemoryNodeManager(NODE_1, NODE_2, NODE_3), new NodeSchedulerConfig().setIncludeCoordinator(true), new NodeTaskMap(new FinalizerService())).createNodeSelector(testSessionBuilder().build(), Optional.empty()), newScheduledThreadPool(10, threadsNamed("task-notification-%s")), DataSize.of(32, DataSize.Unit.MEGABYTE), - 1); + maxWritersNodesCount); } private static TaskStatus buildTaskStatus(boolean isOutputBufferOverUtilized, long outputDataSize) + { + return buildTaskStatus(isOutputBufferOverUtilized, outputDataSize, Optional.of(1), DataSize.of(32, DataSize.Unit.MEGABYTE)); + } + + private static TaskStatus buildTaskStatus(int maxWriterCount, DataSize physicalWrittenDataSize) + { + return buildTaskStatus(true, 12345L, Optional.of(maxWriterCount), physicalWrittenDataSize); + } + + private static TaskStatus buildTaskStatus(boolean isOutputBufferOverUtilized, long outputDataSize, Optional maxWriterCount, DataSize physicalWrittenDataSize) { return new TaskStatus( TaskId.valueOf("taskId"), @@ -150,7 +226,8 @@ private static TaskStatus buildTaskStatus(boolean isOutputBufferOverUtilized, lo 0, new OutputBufferStatus(OptionalLong.empty(), isOutputBufferOverUtilized, false), DataSize.ofBytes(outputDataSize), - DataSize.of(32, DataSize.Unit.MEGABYTE), + physicalWrittenDataSize, + maxWriterCount, DataSize.of(1, DataSize.Unit.MEGABYTE), DataSize.of(1, DataSize.Unit.MEGABYTE), DataSize.of(0, DataSize.Unit.MEGABYTE), diff --git a/core/trino-main/src/test/java/io/trino/memory/TestLeastWastedEffortTaskLowMemoryKiller.java b/core/trino-main/src/test/java/io/trino/memory/TestLeastWastedEffortTaskLowMemoryKiller.java index 62ee915fc0d5..3c4c323d205a 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestLeastWastedEffortTaskLowMemoryKiller.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestLeastWastedEffortTaskLowMemoryKiller.java @@ -233,6 +233,7 @@ private static TaskInfo buildTaskInfo(TaskId taskId, TaskState state, Duration s OutputBufferStatus.initial(), DataSize.of(0, DataSize.Unit.MEGABYTE), DataSize.of(1, DataSize.Unit.MEGABYTE), + Optional.of(1), DataSize.of(1, DataSize.Unit.MEGABYTE), DataSize.of(1, DataSize.Unit.MEGABYTE), DataSize.of(0, DataSize.Unit.MEGABYTE), @@ -294,6 +295,7 @@ private static TaskInfo buildTaskInfo(TaskId taskId, TaskState state, Duration s 0, new Duration(0, MILLISECONDS), DataSize.ofBytes(0), + Optional.empty(), 0, new Duration(0, MILLISECONDS), ImmutableList.of()), diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java b/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java index 540528144f3b..654d6aa0cabc 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java @@ -173,6 +173,7 @@ public void testHappyPath() // client should have sent only 3 requests: one to get all pages, one to acknowledge and one to get the done signal assertStatus(status.getPageBufferClientStatuses().get(0), location, "closed", 3, 3, 3, "not scheduled"); + assertEquals(status.getRequestDuration().getDigest().getCount(), 2.0); exchangeClient.close(); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTaskStats.java b/core/trino-main/src/test/java/io/trino/operator/TestTaskStats.java index b6c73349d0d4..a77581ac7305 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTaskStats.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTaskStats.java @@ -21,6 +21,8 @@ import org.joda.time.DateTime; import org.testng.annotations.Test; +import java.util.Optional; + import static io.trino.operator.TestPipelineStats.assertExpectedPipelineStats; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.joda.time.DateTimeZone.UTC; @@ -78,6 +80,7 @@ public class TestTaskStats new Duration(272, NANOSECONDS), DataSize.ofBytes(25), + Optional.of(2), 26, new Duration(27, NANOSECONDS), diff --git a/core/trino-main/src/test/java/io/trino/operator/output/BenchmarkPartitionedOutputOperator.java b/core/trino-main/src/test/java/io/trino/operator/output/BenchmarkPartitionedOutputOperator.java index 556692be19e8..3c5615bc7f71 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/BenchmarkPartitionedOutputOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/BenchmarkPartitionedOutputOperator.java @@ -464,7 +464,10 @@ private PartitionedOutputOperator createPartitionedOutputOperator() OptionalInt.empty(), buffer, MAX_PARTITION_BUFFER_SIZE, - POSITIONS_APPENDER_FACTORY); + POSITIONS_APPENDER_FACTORY, + Optional.empty(), + newSimpleAggregatedMemoryContext(), + 0); return (PartitionedOutputOperator) operatorFactory .createOutputOperator(0, new PlanNodeId("plan-node-0"), types, Function.identity(), serdeFactory) .createOperator(createDriverContext()); diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java index 217c990f61c5..ce0cc3545e27 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java @@ -32,6 +32,7 @@ import io.trino.operator.BucketPartitionFunction; import io.trino.operator.DriverContext; import io.trino.operator.OperatorContext; +import io.trino.operator.OperatorFactory; import io.trino.operator.OutputFactory; import io.trino.operator.PartitionFunction; import io.trino.spi.Page; @@ -74,6 +75,7 @@ import static io.trino.block.BlockAssertions.createLongsBlock; import static io.trino.block.BlockAssertions.createRandomBlockForType; import static io.trino.block.BlockAssertions.createRepeatedValuesBlock; +import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.CharType.createCharType; @@ -140,7 +142,7 @@ public void testOutputForEmptyPage() Page page = new Page(createLongsBlock(ImmutableList.of())); pagePartitioner.partitionPage(page); - pagePartitioner.forceFlush(); + pagePartitioner.close(); List partitioned = readLongs(outputBuffer.getEnqueuedDeserialized(), 0); assertThat(partitioned).isEmpty(); @@ -378,7 +380,7 @@ private void testOutputEqualsInput(Type type, PartitioningMode mode1, Partitioni mode1.partitionPage(pagePartitioner, input); mode2.partitionPage(pagePartitioner, input); - pagePartitioner.forceFlush(); + pagePartitioner.close(); List partitioned = readChannel(outputBuffer.getEnqueuedDeserialized(), 1, type); assertThat(partitioned).containsExactlyInAnyOrderElementsOf(expected); // output of the PagePartitioner can be reordered @@ -437,7 +439,7 @@ private static void processPages(PagePartitioner pagePartitioner, PartitioningMo for (Page page : pages) { partitioningMode.partitionPage(pagePartitioner, page); } - pagePartitioner.forceFlush(); + pagePartitioner.close(); } private static List readLongs(Stream pages, int channel) @@ -592,11 +594,15 @@ public PartitionedOutputOperator buildPartitionedOutputOperator() nullChannel, outputBuffer, PARTITION_MAX_MEMORY, - POSITIONS_APPENDER_FACTORY); - - return (PartitionedOutputOperator) operatorFactory - .createOutputOperator(0, new PlanNodeId("plan-node-0"), types, Function.identity(), PAGES_SERDE_FACTORY) + POSITIONS_APPENDER_FACTORY, + Optional.empty(), + newSimpleAggregatedMemoryContext(), + 1); + OperatorFactory factory = operatorFactory.createOutputOperator(0, new PlanNodeId("plan-node-0"), types, Function.identity(), PAGES_SERDE_FACTORY); + PartitionedOutputOperator operator = (PartitionedOutputOperator) factory .createOperator(driverContext); + factory.noMoreOperators(); + return operator; } public PagePartitioner build() @@ -605,7 +611,7 @@ public PagePartitioner build() OperatorContext operatorContext = driverContext.addOperatorContext(0, new PlanNodeId("plan-node-0"), PartitionedOutputOperator.class.getSimpleName()); - return new PagePartitioner( + PagePartitioner pagePartitioner = new PagePartitioner( partitionFunction, partitionChannels, partitionConstants, @@ -615,9 +621,12 @@ public PagePartitioner build() PAGES_SERDE_FACTORY, types, PARTITION_MAX_MEMORY, - operatorContext, POSITIONS_APPENDER_FACTORY, - Optional.empty()); + Optional.empty(), + newSimpleAggregatedMemoryContext()); + pagePartitioner.setupOperator(operatorContext); + + return pagePartitioner; } private DriverContext buildDriverContext() diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java new file mode 100644 index 000000000000..a6cf5e112be4 --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java @@ -0,0 +1,286 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.operator.output; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; +import io.airlift.slice.Slice; +import io.airlift.units.DataSize; +import io.trino.execution.StateMachine; +import io.trino.execution.buffer.BufferResult; +import io.trino.execution.buffer.BufferState; +import io.trino.execution.buffer.OutputBuffer; +import io.trino.execution.buffer.OutputBufferInfo; +import io.trino.execution.buffer.OutputBufferStatus; +import io.trino.execution.buffer.OutputBuffers; +import io.trino.execution.buffer.PipelinedOutputBuffers.OutputBufferId; +import io.trino.execution.buffer.TestingPagesSerdeFactory; +import io.trino.memory.context.AggregatedMemoryContext; +import io.trino.operator.BucketPartitionFunction; +import io.trino.operator.DriverContext; +import io.trino.operator.Operator; +import io.trino.operator.exchange.PageChannelSelector; +import io.trino.operator.output.PartitionedOutputOperator.PartitionedOutputOperatorFactory; +import io.trino.spi.Page; +import io.trino.sql.planner.plan.PlanNodeId; +import io.trino.testing.TestingTaskContext; +import io.trino.type.BlockTypeOperators; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.concurrent.ScheduledExecutorService; +import java.util.stream.Stream; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static io.airlift.concurrent.Threads.threadsNamed; +import static io.trino.SessionTestUtils.TEST_SESSION; +import static io.trino.block.BlockAssertions.createLongsBlock; +import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; +import static io.trino.spi.type.BigintType.BIGINT; +import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.assertEquals; + +public class TestPagePartitionerPool +{ + private ScheduledExecutorService driverYieldExecutor; + + @BeforeClass + public void setUp() + { + driverYieldExecutor = newScheduledThreadPool(0, threadsNamed("TestPagePartitionerPool-driver-yield-%s")); + } + + @AfterClass(alwaysRun = true) + public void destroy() + { + driverYieldExecutor.shutdown(); + } + + @Test + public void testBuffersReusedAcrossSplits() + { + Page split = new Page(createLongsBlock(1)); + // one split fit in the buffer but 2 do not + DataSize maxPagePartitioningBufferSize = DataSize.ofBytes(split.getSizeInBytes() + 1); + + OutputBufferMock outputBuffer = new OutputBufferMock(); + AggregatedMemoryContext memoryContext = newSimpleAggregatedMemoryContext(); + PartitionedOutputOperatorFactory factory = new PartitionedOutputOperatorFactory( + 0, + new PlanNodeId("0"), + ImmutableList.of(BIGINT), + PageChannelSelector.identitySelection(), + new BucketPartitionFunction((page, position) -> 0, new int[1]), + ImmutableList.of(0), + ImmutableList.of(), + false, + OptionalInt.empty(), + outputBuffer, + new TestingPagesSerdeFactory(), + maxPagePartitioningBufferSize, + new PositionsAppenderFactory(new BlockTypeOperators()), + Optional.empty(), + memoryContext, + 2); + + assertEquals(memoryContext.getBytes(), 0); + // first split, too small for a flush + long initialRetainedBytesOneOperator = processSplitsConcurrently(factory, memoryContext, split); + assertEquals(outputBuffer.totalEnqueuedPageCount(), 0); + assertThat(memoryContext.getBytes()).isGreaterThanOrEqualTo(initialRetainedBytesOneOperator + split.getSizeInBytes()); + + // second split makes the split partitioner buffer full so the split is flushed + processSplitsConcurrently(factory, memoryContext, split); + assertEquals(outputBuffer.totalEnqueuedPageCount(), 1); + assertEquals(memoryContext.getBytes(), initialRetainedBytesOneOperator); + + // two splits are processed at once so the use different buffers and do not flush for single split per buffer + long initialRetainedBytesTwoOperators = processSplitsConcurrently(factory, memoryContext, split, split); + assertEquals(outputBuffer.totalEnqueuedPageCount(), 1); + assertThat(memoryContext.getBytes()).isGreaterThanOrEqualTo(initialRetainedBytesTwoOperators + 2 * split.getSizeInBytes()); + + // another pair of splits should flush both buffers + processSplitsConcurrently(factory, memoryContext, split, split); + assertEquals(outputBuffer.totalEnqueuedPageCount(), 3); + assertEquals(memoryContext.getBytes(), initialRetainedBytesTwoOperators); + + // max free buffers is set to 2 so 2 buffers are going to be retained but 2 will be flushed and released + processSplitsConcurrently(factory, memoryContext, split, split, split, split); + assertEquals(outputBuffer.totalEnqueuedPageCount(), 5); + assertThat(memoryContext.getBytes()).isGreaterThanOrEqualTo(initialRetainedBytesTwoOperators + 2 * split.getSizeInBytes()); + + // another pair of splits should flush remaining buffers + processSplitsConcurrently(factory, memoryContext, split, split); + assertEquals(outputBuffer.totalEnqueuedPageCount(), 7); + assertEquals(memoryContext.getBytes(), initialRetainedBytesTwoOperators); + + // noMoreOperators forces buffers to be flushed even though they are not full + processSplitsConcurrently(factory, memoryContext, split); + assertThat(memoryContext.getBytes()).isGreaterThanOrEqualTo(initialRetainedBytesTwoOperators + split.getSizeInBytes()); + Operator operator = factory.createOperator(driverContext()); + factory.noMoreOperators(); + assertEquals(outputBuffer.totalEnqueuedPageCount(), 8); + assertEquals(memoryContext.getBytes(), initialRetainedBytesOneOperator); + + // noMoreOperators was called already so new split are flushed even though they are not full + operator.addInput(split); + operator.finish(); + assertEquals(outputBuffer.totalEnqueuedPageCount(), 9); + // pool is closed, all operators are finished/flushed, the retained memory should be 0 + assertEquals(memoryContext.getBytes(), 0); + } + + private long processSplitsConcurrently(PartitionedOutputOperatorFactory factory, AggregatedMemoryContext memoryContext, Page... splits) + { + List operators = Stream.of(splits) + .map(split -> factory.createOperator(driverContext())) + .collect(toImmutableList()); + + long initialRetainedBytes = memoryContext.getBytes(); + for (int i = 0; i < operators.size(); i++) { + operators.get(i).addInput(splits[i]); + } + operators.forEach(Operator::finish); + return initialRetainedBytes; + } + + private DriverContext driverContext() + { + return TestingTaskContext.builder(directExecutor(), driverYieldExecutor, TEST_SESSION) + .build() + .addPipelineContext(0, true, true, false) + .addDriverContext(); + } + + private static class OutputBufferMock + implements OutputBuffer + { + Map partitionBufferPages = new HashMap<>(); + + public int totalEnqueuedPageCount() + { + return partitionBufferPages.values().stream().mapToInt(Integer::intValue).sum(); + } + + @Override + public void enqueue(int partition, List pages) + { + partitionBufferPages.compute(partition, (key, value) -> value == null ? pages.size() : value + pages.size()); + } + + @Override + public OutputBufferInfo getInfo() + { + throw new UnsupportedOperationException(); + } + + @Override + public BufferState getState() + { + throw new UnsupportedOperationException(); + } + + @Override + public double getUtilization() + { + throw new UnsupportedOperationException(); + } + + @Override + public OutputBufferStatus getStatus() + { + throw new UnsupportedOperationException(); + } + + @Override + public void addStateChangeListener(StateMachine.StateChangeListener stateChangeListener) + { + throw new UnsupportedOperationException(); + } + + @Override + public void setOutputBuffers(OutputBuffers newOutputBuffers) + { + throw new UnsupportedOperationException(); + } + + @Override + public ListenableFuture get(OutputBufferId bufferId, long token, DataSize maxSize) + { + throw new UnsupportedOperationException(); + } + + @Override + public void acknowledge(OutputBufferId bufferId, long token) + { + throw new UnsupportedOperationException(); + } + + @Override + public void destroy(OutputBufferId bufferId) + { + throw new UnsupportedOperationException(); + } + + @Override + public ListenableFuture isFull() + { + throw new UnsupportedOperationException(); + } + + @Override + public void enqueue(List pages) + { + throw new UnsupportedOperationException(); + } + + @Override + public void setNoMorePages() + { + throw new UnsupportedOperationException(); + } + + @Override + public void destroy() + { + throw new UnsupportedOperationException(); + } + + @Override + public void abort() + { + throw new UnsupportedOperationException(); + } + + @Override + public long getPeakMemoryUsage() + { + throw new UnsupportedOperationException(); + } + + @Override + public Optional getFailureCause() + { + throw new UnsupportedOperationException(); + } + } +} diff --git a/core/trino-main/src/test/java/io/trino/server/TestHttpRequestSessionContextFactory.java b/core/trino-main/src/test/java/io/trino/server/TestHttpRequestSessionContextFactory.java index c53f9fd326f0..176a7c5a355a 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestHttpRequestSessionContextFactory.java +++ b/core/trino-main/src/test/java/io/trino/server/TestHttpRequestSessionContextFactory.java @@ -30,8 +30,8 @@ import java.util.Optional; -import static io.trino.SystemSessionProperties.HASH_PARTITION_COUNT; import static io.trino.SystemSessionProperties.JOIN_DISTRIBUTION_TYPE; +import static io.trino.SystemSessionProperties.MAX_HASH_PARTITION_COUNT; import static io.trino.SystemSessionProperties.QUERY_MAX_MEMORY; import static io.trino.client.ProtocolHeaders.TRINO_HEADERS; import static io.trino.client.ProtocolHeaders.createProtocolHeaders; @@ -66,7 +66,7 @@ private static void assertSessionContext(ProtocolHeaders protocolHeaders) .put(protocolHeaders.requestTimeZone(), "Asia/Taipei") .put(protocolHeaders.requestClientInfo(), "client-info") .put(protocolHeaders.requestSession(), QUERY_MAX_MEMORY + "=1GB") - .put(protocolHeaders.requestSession(), JOIN_DISTRIBUTION_TYPE + "=partitioned," + HASH_PARTITION_COUNT + " = 43") + .put(protocolHeaders.requestSession(), JOIN_DISTRIBUTION_TYPE + "=partitioned," + MAX_HASH_PARTITION_COUNT + " = 43") .put(protocolHeaders.requestSession(), "some_session_property=some value with %2C comma") .put(protocolHeaders.requestPreparedStatement(), "query1=select * from foo,query2=select * from bar") .put(protocolHeaders.requestRole(), "system=ROLE{system-role}") @@ -100,7 +100,7 @@ private static void assertSessionContext(ProtocolHeaders protocolHeaders) assertEquals(context.getSystemProperties(), ImmutableMap.of( QUERY_MAX_MEMORY, "1GB", JOIN_DISTRIBUTION_TYPE, "partitioned", - HASH_PARTITION_COUNT, "43", + MAX_HASH_PARTITION_COUNT, "43", "some_session_property", "some value with , comma")); assertEquals(context.getPreparedStatements(), ImmutableMap.of("query1", "select * from foo", "query2", "select * from bar")); assertEquals(context.getSelectedRole(), new SelectedRole(SelectedRole.Type.ROLE, Optional.of("system-role"))); diff --git a/core/trino-main/src/test/java/io/trino/server/TestQuerySessionSupplier.java b/core/trino-main/src/test/java/io/trino/server/TestQuerySessionSupplier.java index 4531de884092..99b09b9c5880 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestQuerySessionSupplier.java +++ b/core/trino-main/src/test/java/io/trino/server/TestQuerySessionSupplier.java @@ -40,8 +40,8 @@ import java.util.Locale; import java.util.Optional; -import static io.trino.SystemSessionProperties.HASH_PARTITION_COUNT; import static io.trino.SystemSessionProperties.JOIN_DISTRIBUTION_TYPE; +import static io.trino.SystemSessionProperties.MAX_HASH_PARTITION_COUNT; import static io.trino.SystemSessionProperties.QUERY_MAX_MEMORY; import static io.trino.client.ProtocolHeaders.TRINO_HEADERS; import static io.trino.metadata.MetadataManager.createTestMetadataManager; @@ -65,7 +65,7 @@ public class TestQuerySessionSupplier .put(TRINO_HEADERS.requestClientInfo(), "client-info") .put(TRINO_HEADERS.requestClientTags(), "tag1,tag2 ,tag3, tag2") .put(TRINO_HEADERS.requestSession(), QUERY_MAX_MEMORY + "=1GB") - .put(TRINO_HEADERS.requestSession(), JOIN_DISTRIBUTION_TYPE + "=partitioned," + HASH_PARTITION_COUNT + " = 43") + .put(TRINO_HEADERS.requestSession(), JOIN_DISTRIBUTION_TYPE + "=partitioned," + MAX_HASH_PARTITION_COUNT + " = 43") .put(TRINO_HEADERS.requestPreparedStatement(), "query1=select * from foo,query2=select * from bar") .build()); private static final HttpRequestSessionContextFactory SESSION_CONTEXT_FACTORY = new HttpRequestSessionContextFactory( @@ -95,7 +95,7 @@ public void testCreateSession() assertEquals(session.getSystemProperties(), ImmutableMap.builder() .put(QUERY_MAX_MEMORY, "1GB") .put(JOIN_DISTRIBUTION_TYPE, "partitioned") - .put(HASH_PARTITION_COUNT, "43") + .put(MAX_HASH_PARTITION_COUNT, "43") .buildOrThrow()); assertEquals(session.getPreparedStatements(), ImmutableMap.builder() .put("query1", "select * from foo") diff --git a/core/trino-main/src/test/java/io/trino/server/TestSessionPropertyDefaults.java b/core/trino-main/src/test/java/io/trino/server/TestSessionPropertyDefaults.java index c5888e4b5ad4..5435d1ef4bab 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestSessionPropertyDefaults.java +++ b/core/trino-main/src/test/java/io/trino/server/TestSessionPropertyDefaults.java @@ -33,8 +33,8 @@ import java.util.Optional; -import static io.trino.SystemSessionProperties.HASH_PARTITION_COUNT; import static io.trino.SystemSessionProperties.JOIN_DISTRIBUTION_TYPE; +import static io.trino.SystemSessionProperties.MAX_HASH_PARTITION_COUNT; import static io.trino.SystemSessionProperties.QUERY_MAX_MEMORY; import static io.trino.SystemSessionProperties.QUERY_MAX_TOTAL_MEMORY; import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; @@ -77,14 +77,14 @@ public void testApplyDefaultProperties() .setIdentity(Identity.ofUser("testUser")) .setSystemProperty(QUERY_MAX_MEMORY, "1GB") // Override this default system property .setSystemProperty(JOIN_DISTRIBUTION_TYPE, "partitioned") - .setSystemProperty(HASH_PARTITION_COUNT, "43") + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "43") .setCatalogSessionProperty(TEST_CATALOG_NAME, "explicit_set", "explicit_set") // Override this default catalog property .build(); assertEquals(session.getSystemProperties(), ImmutableMap.builder() .put(QUERY_MAX_MEMORY, "1GB") .put(JOIN_DISTRIBUTION_TYPE, "partitioned") - .put(HASH_PARTITION_COUNT, "43") + .put(MAX_HASH_PARTITION_COUNT, "43") .buildOrThrow()); assertEquals( session.getCatalogProperties(), @@ -97,7 +97,7 @@ public void testApplyDefaultProperties() assertEquals(session.getSystemProperties(), ImmutableMap.builder() .put(QUERY_MAX_MEMORY, "1GB") // User provided value overrides default value .put(JOIN_DISTRIBUTION_TYPE, "partitioned") // User provided value is used - .put(HASH_PARTITION_COUNT, "43") // User provided value is used + .put(MAX_HASH_PARTITION_COUNT, "43") // User provided value is used .put(QUERY_MAX_TOTAL_MEMORY, "2GB") // Default value is used .buildOrThrow()); assertEquals( diff --git a/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java b/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java index 330a566cb052..7d87689e6e89 100644 --- a/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java +++ b/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java @@ -772,6 +772,7 @@ private TaskStatus buildTaskStatus() initialTaskStatus.getOutputBufferStatus(), initialTaskStatus.getOutputDataSize(), initialTaskStatus.getPhysicalWrittenDataSize(), + initialTaskStatus.getMaxWriterCount(), initialTaskStatus.getMemoryReservation(), initialTaskStatus.getPeakMemoryReservation(), initialTaskStatus.getRevocableMemoryReservation(), diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestAddDynamicFilterSource.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestAddDynamicFilterSource.java index f87227b16042..4ad7f0f9018f 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestAddDynamicFilterSource.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestAddDynamicFilterSource.java @@ -161,6 +161,7 @@ public void testInnerJoinWithUnionAllOnBuild() ImmutableSet.of(), Optional.empty(), ImmutableList.of("SUPPLIER_SK"), + Optional.empty(), project(tableScan("supplier", ImmutableMap.of("SUPPLIER_SK_1", "suppkey"))), project(tableScan("supplier", ImmutableMap.of("SUPPLIER_SK_2", "suppkey"))))))))))); @@ -183,6 +184,7 @@ public void testInnerJoinWithUnionAllOnBuild() ImmutableSet.of(), Optional.empty(), ImmutableList.of("SUPPLIER_SK"), + Optional.empty(), exchange(project(tableScan("supplier", ImmutableMap.of("SUPPLIER_SK_1", "suppkey")))), exchange(project(tableScan("supplier", ImmutableMap.of("SUPPLIER_SK_2", "suppkey"))))))))); } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java index ef5d17a2ddf9..16bc55c7d079 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java @@ -29,7 +29,6 @@ import io.trino.sql.planner.OptimizerConfig.JoinDistributionType; import io.trino.sql.planner.OptimizerConfig.JoinReorderingStrategy; import io.trino.sql.planner.assertions.BasePlanTest; -import io.trino.sql.planner.assertions.ExpressionMatcher; import io.trino.sql.planner.assertions.PlanMatchPattern; import io.trino.sql.planner.assertions.RowNumberSymbolMatcher; import io.trino.sql.planner.optimizations.AddLocalExchanges; @@ -106,6 +105,7 @@ import static io.trino.sql.planner.assertions.PlanMatchPattern.assignUniqueId; import static io.trino.sql.planner.assertions.PlanMatchPattern.constrainedTableScan; import static io.trino.sql.planner.assertions.PlanMatchPattern.constrainedTableScanWithTableLayout; +import static io.trino.sql.planner.assertions.PlanMatchPattern.correlatedJoin; import static io.trino.sql.planner.assertions.PlanMatchPattern.equiJoinClause; import static io.trino.sql.planner.assertions.PlanMatchPattern.exchange; import static io.trino.sql.planner.assertions.PlanMatchPattern.expression; @@ -813,6 +813,31 @@ public void testCorrelatedJoinWithTopN() anyTree(tableScan("nation", ImmutableMap.of("nation_name", "name", "nation_regionkey", "regionkey"))))))))); } + @Test + public void testCorrelatedJoinWithNullCondition() + { + assertPlan( + "SELECT regionkey, n.name FROM region LEFT JOIN LATERAL (SELECT name FROM nation) n ON NULL", + CREATED, + anyTree( + correlatedJoin( + List.of("r_row_number", "r_regionkey", "r_name", "r_comment"), + "CAST(null AS boolean)", + tableScan("region", Map.of( + "r_row_number", "row_number", + "r_regionkey", "regionkey", + "r_name", "name", + "r_comment", "comment")), + anyTree(tableScan("nation"))))); + assertPlan( + "SELECT regionkey, n.name FROM region LEFT JOIN LATERAL (SELECT name FROM nation) n ON NULL", + any( + join(LEFT, builder -> builder + .equiCriteria(List.of()) + .left(tableScan("region")) + .right(values("name"))))); + } + @Test public void testCorrelatedScalarSubqueryInSelect() { @@ -1109,6 +1134,14 @@ public void testRemovesTrivialFilters() "SELECT * FROM nation WHERE 1 = 0", output( values("nationkey", "name", "regionkey", "comment"))); + assertPlan( + "SELECT * FROM nation WHERE null", + output( + values("nationkey", "name", "regionkey", "comment"))); + assertPlan( + "SELECT * FROM nation WHERE nationkey = null", + output( + values("nationkey", "name", "regionkey", "comment"))); } @Test @@ -1488,7 +1521,7 @@ public void testOffset() "SELECT name FROM nation OFFSET 2 ROWS", any( strictProject( - ImmutableMap.of("name", new ExpressionMatcher("name")), + ImmutableMap.of("name", expression("name")), filter( "row_num > BIGINT '2'", rowNumber( @@ -1502,7 +1535,7 @@ public void testOffset() "SELECT name FROM nation ORDER BY regionkey OFFSET 2 ROWS", any( strictProject( - ImmutableMap.of("name", new ExpressionMatcher("name")), + ImmutableMap.of("name", expression("name")), filter( "row_num > BIGINT '2'", rowNumber( @@ -1519,7 +1552,7 @@ public void testOffset() "SELECT name FROM nation ORDER BY regionkey OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY", any( strictProject( - ImmutableMap.of("name", new ExpressionMatcher("name")), + ImmutableMap.of("name", expression("name")), filter( "row_num > BIGINT '2'", rowNumber( @@ -1538,7 +1571,7 @@ public void testOffset() "SELECT name FROM nation OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY", any( strictProject( - ImmutableMap.of("name", new ExpressionMatcher("name")), + ImmutableMap.of("name", expression("name")), filter( "row_num > BIGINT '2'", rowNumber( @@ -1558,7 +1591,7 @@ public void testWithTies() "SELECT name, regionkey FROM nation ORDER BY regionkey FETCH FIRST 6 ROWS WITH TIES", any( strictProject( - ImmutableMap.of("name", new ExpressionMatcher("name"), "regionkey", new ExpressionMatcher("regionkey")), + ImmutableMap.of("name", expression("name"), "regionkey", expression("regionkey")), topNRanking( pattern -> pattern .specification( @@ -1578,14 +1611,14 @@ public void testWithTies() "SELECT name, regionkey FROM nation ORDER BY regionkey OFFSET 10 ROWS FETCH FIRST 6 ROWS WITH TIES", any( strictProject( - ImmutableMap.of("name", new ExpressionMatcher("name"), "regionkey", new ExpressionMatcher("regionkey")), + ImmutableMap.of("name", expression("name"), "regionkey", expression("regionkey")), filter( "row_num > BIGINT '10'", rowNumber( pattern -> pattern .partitionBy(ImmutableList.of()), strictProject( - ImmutableMap.of("name", new ExpressionMatcher("name"), "regionkey", new ExpressionMatcher("regionkey")), + ImmutableMap.of("name", expression("name"), "regionkey", expression("regionkey")), topNRanking( pattern -> pattern .specification( diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapCastInComparison.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapCastInComparison.java index b55b5594e39a..73ced38af4ec 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapCastInComparison.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapCastInComparison.java @@ -791,32 +791,19 @@ private void testNoUnwrap(String inputType, String inputPredicate, String expect private void testNoUnwrap(Session session, String inputType, String inputPredicate, String expectedCastType) { - String sql = format("SELECT * FROM (VALUES CAST(NULL AS %s)) t(a) WHERE a %s", inputType, inputPredicate); - try { - assertPlan(sql, - session, - output( - filter(format("CAST(a AS %s) %s", expectedCastType, inputPredicate), - values("a")))); - } - catch (Throwable e) { - e.addSuppressed(new Exception("Query: " + sql)); - throw e; - } + assertPlan(format("SELECT * FROM (VALUES CAST(NULL AS %s)) t(a) WHERE a %s", inputType, inputPredicate), + session, + output( + filter(format("CAST(a AS %s) %s", expectedCastType, inputPredicate), + values("a")))); } private void testRemoveFilter(String inputType, String inputPredicate) { - String sql = format("SELECT * FROM (VALUES CAST(NULL AS %s)) t(a) WHERE %s", inputType, inputPredicate); - try { - assertPlan(sql, - output( - values("a"))); - } - catch (Throwable e) { - e.addSuppressed(new Exception("Query: " + sql)); - throw e; - } + assertPlan(format("SELECT * FROM (VALUES CAST(NULL AS %s)) t(a) WHERE %s AND rand() = 42", inputType, inputPredicate), + output( + filter("rand() = 42e0", + values("a")))); } private void testUnwrap(String inputType, String inputPredicate, String expectedPredicate) @@ -826,18 +813,11 @@ private void testUnwrap(String inputType, String inputPredicate, String expected private void testUnwrap(Session session, String inputType, String inputPredicate, String expectedPredicate) { - String sql = format("SELECT * FROM (VALUES CAST(NULL AS %s)) t(a) WHERE %s", inputType, inputPredicate); - try { - assertPlan(sql, - session, - output( - filter(expectedPredicate, - values("a")))); - } - catch (Throwable e) { - e.addSuppressed(new Exception("Query: " + sql)); - throw e; - } + assertPlan(format("SELECT * FROM (VALUES CAST(NULL AS %s)) t(a) WHERE %s OR rand() = 42", inputType, inputPredicate), + session, + output( + filter(format("%s OR rand() = 42e0", expectedPredicate), + values("a")))); } private static Session withZone(Session session, TimeZoneKey timeZoneKey) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java index cdcc083877aa..42c84ece036e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java @@ -142,11 +142,17 @@ protected void assertPlan(@Language("SQL") String sql, LogicalPlanner.Stage stag protected void assertPlan(@Language("SQL") String sql, LogicalPlanner.Stage stage, PlanMatchPattern pattern, List optimizers) { - queryRunner.inTransaction(transactionSession -> { - Plan actualPlan = queryRunner.createPlan(transactionSession, sql, optimizers, stage, WarningCollector.NOOP); - PlanAssert.assertPlan(transactionSession, queryRunner.getMetadata(), queryRunner.getFunctionManager(), queryRunner.getStatsCalculator(), actualPlan, pattern); - return null; - }); + try { + queryRunner.inTransaction(transactionSession -> { + Plan actualPlan = queryRunner.createPlan(transactionSession, sql, optimizers, stage, WarningCollector.NOOP); + PlanAssert.assertPlan(transactionSession, queryRunner.getMetadata(), queryRunner.getFunctionManager(), queryRunner.getStatsCalculator(), actualPlan, pattern); + return null; + }); + } + catch (Throwable e) { + e.addSuppressed(new Exception("Query: " + sql)); + throw e; + } } protected void assertDistributedPlan(@Language("SQL") String sql, PlanMatchPattern pattern) @@ -178,21 +184,33 @@ protected void assertMinimallyOptimizedPlan(@Language("SQL") String sql, PlanMat protected void assertPlanWithSession(@Language("SQL") String sql, Session session, boolean forceSingleNode, PlanMatchPattern pattern) { - queryRunner.inTransaction(session, transactionSession -> { - Plan actualPlan = queryRunner.createPlan(transactionSession, sql, OPTIMIZED_AND_VALIDATED, forceSingleNode, WarningCollector.NOOP); - PlanAssert.assertPlan(transactionSession, queryRunner.getMetadata(), queryRunner.getFunctionManager(), queryRunner.getStatsCalculator(), actualPlan, pattern); - return null; - }); + try { + queryRunner.inTransaction(session, transactionSession -> { + Plan actualPlan = queryRunner.createPlan(transactionSession, sql, OPTIMIZED_AND_VALIDATED, forceSingleNode, WarningCollector.NOOP); + PlanAssert.assertPlan(transactionSession, queryRunner.getMetadata(), queryRunner.getFunctionManager(), queryRunner.getStatsCalculator(), actualPlan, pattern); + return null; + }); + } + catch (Throwable e) { + e.addSuppressed(new Exception("Query: " + sql)); + throw e; + } } protected void assertPlanWithSession(@Language("SQL") String sql, Session session, boolean forceSingleNode, PlanMatchPattern pattern, Consumer planValidator) { - queryRunner.inTransaction(session, transactionSession -> { - Plan actualPlan = queryRunner.createPlan(transactionSession, sql, OPTIMIZED_AND_VALIDATED, forceSingleNode, WarningCollector.NOOP); - PlanAssert.assertPlan(transactionSession, queryRunner.getMetadata(), queryRunner.getFunctionManager(), queryRunner.getStatsCalculator(), actualPlan, pattern); - planValidator.accept(actualPlan); - return null; - }); + try { + queryRunner.inTransaction(session, transactionSession -> { + Plan actualPlan = queryRunner.createPlan(transactionSession, sql, OPTIMIZED_AND_VALIDATED, forceSingleNode, WarningCollector.NOOP); + PlanAssert.assertPlan(transactionSession, queryRunner.getMetadata(), queryRunner.getFunctionManager(), queryRunner.getStatsCalculator(), actualPlan, pattern); + planValidator.accept(actualPlan); + return null; + }); + } + catch (Throwable e) { + e.addSuppressed(new Exception("Query: " + sql)); + throw e; + } } protected Plan plan(@Language("SQL") String sql) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/CorrelatedJoinMatcher.java b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/CorrelatedJoinMatcher.java new file mode 100644 index 000000000000..56aa90730d5b --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/CorrelatedJoinMatcher.java @@ -0,0 +1,65 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.sql.planner.assertions; + +import io.trino.Session; +import io.trino.cost.StatsProvider; +import io.trino.metadata.Metadata; +import io.trino.sql.DynamicFilters; +import io.trino.sql.planner.plan.CorrelatedJoinNode; +import io.trino.sql.planner.plan.PlanNode; +import io.trino.sql.tree.Expression; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static io.trino.sql.DynamicFilters.extractDynamicFilters; +import static io.trino.sql.ExpressionUtils.combineConjuncts; +import static java.util.Objects.requireNonNull; + +final class CorrelatedJoinMatcher + implements Matcher +{ + private final Expression filter; + + CorrelatedJoinMatcher(Expression filter) + { + this.filter = requireNonNull(filter, "filter is null"); + } + + @Override + public boolean shapeMatches(PlanNode node) + { + // However this is used for CorrelatedJoinNode only + return true; + } + + @Override + public MatchResult detailMatches(PlanNode node, StatsProvider stats, Session session, Metadata metadata, SymbolAliases symbolAliases) + { + if (!(node instanceof CorrelatedJoinNode correlatedJoinNode)) { + throw new IllegalStateException("This is a detailed matcher for CorrelatedJoinNode, got: " + node); + } + Expression filter = correlatedJoinNode.getFilter(); + ExpressionVerifier verifier = new ExpressionVerifier(symbolAliases); + DynamicFilters.ExtractResult extractResult = extractDynamicFilters(filter); + return new MatchResult(verifier.process(combineConjuncts(metadata, extractResult.getStaticConjuncts()), filter)); + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("filter", filter) + .toString(); + } +} diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/ExchangeMatcher.java b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/ExchangeMatcher.java index a13983673740..3f81324456e9 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/ExchangeMatcher.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/ExchangeMatcher.java @@ -43,6 +43,7 @@ final class ExchangeMatcher private final List orderBy; private final Set partitionedBy; private final Optional>> inputs; + private final Optional> partitionCount; public ExchangeMatcher( ExchangeNode.Scope scope, @@ -50,7 +51,8 @@ public ExchangeMatcher( Optional partitioningHandle, List orderBy, Set partitionedBy, - Optional>> inputs) + Optional>> inputs, + Optional> partitionCount) { this.scope = requireNonNull(scope, "scope is null"); this.type = requireNonNull(type, "type is null"); @@ -58,6 +60,7 @@ public ExchangeMatcher( this.orderBy = requireNonNull(orderBy, "orderBy is null"); this.partitionedBy = requireNonNull(partitionedBy, "partitionedBy is null"); this.inputs = requireNonNull(inputs, "inputs is null"); + this.partitionCount = requireNonNull(partitionCount, "partitionCount is null"); } @Override @@ -113,6 +116,11 @@ public MatchResult detailMatches(PlanNode node, StatsProvider stats, Session ses } } + if (partitionCount.isPresent() + && !partitionCount.get().equals(exchangeNode.getPartitioningScheme().getPartitionCount())) { + return NO_MATCH; + } + return MatchResult.match(); } @@ -126,6 +134,7 @@ public String toString() .add("orderBy", orderBy) .add("partitionedBy", partitionedBy); inputs.ifPresent(inputs -> string.add("inputs", inputs)); + partitionCount.ifPresent(partitionCount -> string.add("partitionCount", partitionCount)); return string.toString(); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/ExpressionMatcher.java b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/ExpressionMatcher.java index 6b6bed794455..890e727c8c5c 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/ExpressionMatcher.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/ExpressionMatcher.java @@ -25,6 +25,7 @@ import io.trino.sql.tree.Expression; import io.trino.sql.tree.InPredicate; import io.trino.sql.tree.SymbolReference; +import org.intellij.lang.annotations.Language; import java.util.List; import java.util.Map; @@ -40,13 +41,13 @@ public class ExpressionMatcher private final String sql; private final Expression expression; - public ExpressionMatcher(String expression) + ExpressionMatcher(@Language("SQL") String expression) { this.sql = requireNonNull(expression, "expression is null"); this.expression = expression(expression); } - public ExpressionMatcher(Expression expression) + ExpressionMatcher(Expression expression) { this.expression = requireNonNull(expression, "expression is null"); this.sql = expression.toString(); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/PlanMatchPattern.java b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/PlanMatchPattern.java index 42a5fdcb833a..3befae186b96 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/PlanMatchPattern.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/PlanMatchPattern.java @@ -508,23 +508,23 @@ public static PlanMatchPattern semiJoin(String sourceSymbolAlias, String filteri return node(SemiJoinNode.class, source, filtering).with(new SemiJoinMatcher(sourceSymbolAlias, filteringSymbolAlias, outputAlias, distributionType, hasDynamicFilter)); } - public static PlanMatchPattern spatialJoin(String expectedFilter, PlanMatchPattern left, PlanMatchPattern right) + public static PlanMatchPattern spatialJoin(@Language("SQL") String expectedFilter, PlanMatchPattern left, PlanMatchPattern right) { return spatialJoin(expectedFilter, Optional.empty(), left, right); } - public static PlanMatchPattern spatialJoin(String expectedFilter, Optional kdbTree, PlanMatchPattern left, PlanMatchPattern right) + public static PlanMatchPattern spatialJoin(@Language("SQL") String expectedFilter, Optional kdbTree, PlanMatchPattern left, PlanMatchPattern right) { return spatialJoin(expectedFilter, kdbTree, Optional.empty(), left, right); } - public static PlanMatchPattern spatialJoin(String expectedFilter, Optional kdbTree, Optional> outputSymbols, PlanMatchPattern left, PlanMatchPattern right) + public static PlanMatchPattern spatialJoin(@Language("SQL") String expectedFilter, Optional kdbTree, Optional> outputSymbols, PlanMatchPattern left, PlanMatchPattern right) { return node(SpatialJoinNode.class, left, right).with( new SpatialJoinMatcher(SpatialJoinNode.Type.INNER, PlanBuilder.expression(expectedFilter), kdbTree, outputSymbols)); } - public static PlanMatchPattern spatialLeftJoin(String expectedFilter, PlanMatchPattern left, PlanMatchPattern right) + public static PlanMatchPattern spatialLeftJoin(@Language("SQL") String expectedFilter, PlanMatchPattern left, PlanMatchPattern right) { return node(SpatialJoinNode.class, left, right).with( new SpatialJoinMatcher(SpatialJoinNode.Type.LEFT, PlanBuilder.expression(expectedFilter), Optional.empty(), Optional.empty())); @@ -617,7 +617,12 @@ public static PlanMatchPattern exchange( Optional>> inputs, PlanMatchPattern... sources) { - return exchange(scope, type, partitioningHandle, orderBy, partitionedBy, inputs, ImmutableList.of(), sources); + return exchange(scope, type, partitioningHandle, orderBy, partitionedBy, inputs, ImmutableList.of(), Optional.empty(), sources); + } + + public static PlanMatchPattern exchange(ExchangeNode.Scope scope, Optional partitionCount, PlanMatchPattern... sources) + { + return exchange(scope, Optional.empty(), Optional.empty(), ImmutableList.of(), ImmutableSet.of(), Optional.empty(), ImmutableList.of(), Optional.of(partitionCount), sources); } public static PlanMatchPattern exchange( @@ -628,10 +633,11 @@ public static PlanMatchPattern exchange( Set partitionedBy, Optional>> inputs, List outputSymbolAliases, + Optional> partitionCount, PlanMatchPattern... sources) { PlanMatchPattern result = node(ExchangeNode.class, sources) - .with(new ExchangeMatcher(scope, type, partitioningHandle, orderBy, partitionedBy, inputs)); + .with(new ExchangeMatcher(scope, type, partitioningHandle, orderBy, partitionedBy, inputs, partitionCount)); for (int i = 0; i < outputSymbolAliases.size(); i++) { String outputSymbol = outputSymbolAliases.get(i); @@ -719,6 +725,12 @@ public static PlanMatchPattern correlatedJoin(List correlationSymbolAlia .with(new CorrelationMatcher(correlationSymbolAliases)); } + public static PlanMatchPattern correlatedJoin(List correlationSymbolAliases, @Language("SQL") String filter, PlanMatchPattern inputPattern, PlanMatchPattern subqueryPattern) + { + return correlatedJoin(correlationSymbolAliases, inputPattern, subqueryPattern) + .with(new CorrelatedJoinMatcher(PlanBuilder.expression(filter))); + } + public static PlanMatchPattern groupId(List> groupingSets, String groupIdSymbol, PlanMatchPattern source) { return groupId(groupingSets, ImmutableList.of(), groupIdSymbol, source); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestImplementLimitWithTies.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestImplementLimitWithTies.java index 7cef893773d6..f4cb445898b3 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestImplementLimitWithTies.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestImplementLimitWithTies.java @@ -17,12 +17,12 @@ import com.google.common.collect.ImmutableMap; import io.trino.spi.connector.SortOrder; import io.trino.sql.planner.Symbol; -import io.trino.sql.planner.assertions.ExpressionMatcher; import io.trino.sql.planner.iterative.rule.test.BaseRuleTest; import org.testng.annotations.Test; import java.util.Optional; +import static io.trino.sql.planner.assertions.PlanMatchPattern.expression; import static io.trino.sql.planner.assertions.PlanMatchPattern.filter; import static io.trino.sql.planner.assertions.PlanMatchPattern.functionCall; import static io.trino.sql.planner.assertions.PlanMatchPattern.specification; @@ -47,7 +47,7 @@ public void testReplaceLimitWithTies() }) .matches( strictProject( - ImmutableMap.of("a", new ExpressionMatcher("a"), "b", new ExpressionMatcher("b")), + ImmutableMap.of("a", expression("a"), "b", expression("b")), filter( "rank_num <= BIGINT '2'", window( diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestImplementOffset.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestImplementOffset.java index 103c021b479e..a77cd8ca9309 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestImplementOffset.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestImplementOffset.java @@ -16,11 +16,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.trino.sql.planner.Symbol; -import io.trino.sql.planner.assertions.ExpressionMatcher; import io.trino.sql.planner.assertions.RowNumberSymbolMatcher; import io.trino.sql.planner.iterative.rule.test.BaseRuleTest; import org.testng.annotations.Test; +import static io.trino.sql.planner.assertions.PlanMatchPattern.expression; import static io.trino.sql.planner.assertions.PlanMatchPattern.filter; import static io.trino.sql.planner.assertions.PlanMatchPattern.rowNumber; import static io.trino.sql.planner.assertions.PlanMatchPattern.sort; @@ -45,7 +45,7 @@ public void testReplaceOffsetOverValues() }) .matches( strictProject( - ImmutableMap.of("a", new ExpressionMatcher("a"), "b", new ExpressionMatcher("b")), + ImmutableMap.of("a", expression("a"), "b", expression("b")), filter( "row_num > BIGINT '2'", rowNumber( @@ -70,7 +70,7 @@ public void testReplaceOffsetOverSort() }) .matches( strictProject( - ImmutableMap.of("a", new ExpressionMatcher("a"), "b", new ExpressionMatcher("b")), + ImmutableMap.of("a", expression("a"), "b", expression("b")), filter( "row_num > BIGINT '2'", rowNumber( diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestMergeLimitOverProjectWithSort.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestMergeLimitOverProjectWithSort.java index bc970a37e0fc..5fd8b63a496a 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestMergeLimitOverProjectWithSort.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestMergeLimitOverProjectWithSort.java @@ -16,13 +16,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.trino.sql.planner.Symbol; -import io.trino.sql.planner.assertions.ExpressionMatcher; import io.trino.sql.planner.iterative.rule.test.BaseRuleTest; import io.trino.sql.planner.plan.Assignments; import org.testng.annotations.Test; import java.util.List; +import static io.trino.sql.planner.assertions.PlanMatchPattern.expression; import static io.trino.sql.planner.assertions.PlanMatchPattern.project; import static io.trino.sql.planner.assertions.PlanMatchPattern.sort; import static io.trino.sql.planner.assertions.PlanMatchPattern.topN; @@ -50,7 +50,7 @@ public void testMergeLimitOverProjectWithSort() }) .matches( project( - ImmutableMap.of("b", new ExpressionMatcher("b")), + ImmutableMap.of("b", expression("b")), topN( 1, ImmutableList.of(sort("a", ASCENDING, FIRST)), @@ -94,7 +94,7 @@ public void testLimitWithPreSortedInputs() }) .matches( project( - ImmutableMap.of("b", new ExpressionMatcher("b")), + ImmutableMap.of("b", expression("b")), topN( 1, ImmutableList.of(sort("a", ASCENDING, FIRST)), diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushLimitThroughProject.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushLimitThroughProject.java index da9a820ef4a3..278dd20f6dc4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushLimitThroughProject.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushLimitThroughProject.java @@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableMap; import io.trino.spi.type.RowType; import io.trino.sql.planner.Symbol; -import io.trino.sql.planner.assertions.ExpressionMatcher; import io.trino.sql.planner.iterative.rule.test.BaseRuleTest; import io.trino.sql.planner.plan.Assignments; import io.trino.sql.tree.ArithmeticBinaryExpression; @@ -78,7 +77,7 @@ public void testPushdownLimitWithTiesNNonIdentityProjection() }) .matches( project( - ImmutableMap.of("projectedA", new ExpressionMatcher("a"), "projectedB", new ExpressionMatcher("b")), + ImmutableMap.of("projectedA", expression("a"), "projectedB", expression("b")), limit(1, ImmutableList.of(sort("a", ASCENDING, FIRST)), values("a", "b")))); } @@ -102,7 +101,7 @@ projectedC, new ArithmeticBinaryExpression(ADD, new SymbolReference("a"), new Sy }) .matches( project( - ImmutableMap.of("projectedA", new ExpressionMatcher("a"), "projectedC", new ExpressionMatcher("a + b")), + ImmutableMap.of("projectedA", expression("a"), "projectedC", expression("a + b")), limit(1, ImmutableList.of(sort("a", ASCENDING, FIRST)), values("a", "b")))); } @@ -199,7 +198,7 @@ projectedC, new ArithmeticBinaryExpression(ADD, new SymbolReference("a"), new Sy }) .matches( project( - ImmutableMap.of("projectedA", new ExpressionMatcher("a"), "projectedC", new ExpressionMatcher("a + b")), + ImmutableMap.of("projectedA", expression("a"), "projectedC", expression("a + b")), limit(1, ImmutableList.of(), true, ImmutableList.of("a"), values("a", "b")))); } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushTopNThroughProject.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushTopNThroughProject.java index 159c0cd3cd9f..ef186c52bcf4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushTopNThroughProject.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushTopNThroughProject.java @@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableMap; import io.trino.spi.type.RowType; import io.trino.sql.planner.Symbol; -import io.trino.sql.planner.assertions.ExpressionMatcher; import io.trino.sql.planner.iterative.rule.test.BaseRuleTest; import io.trino.sql.planner.plan.Assignments; import io.trino.sql.tree.ArithmeticBinaryExpression; @@ -65,7 +64,7 @@ public void testPushdownTopNNonIdentityProjection() }) .matches( project( - ImmutableMap.of("projectedA", new ExpressionMatcher("a"), "projectedB", new ExpressionMatcher("b")), + ImmutableMap.of("projectedA", expression("a"), "projectedB", expression("b")), topN(1, ImmutableList.of(sort("a", ASCENDING, FIRST)), values("a", "b")))); } @@ -89,7 +88,7 @@ projectedC, new ArithmeticBinaryExpression(ADD, new SymbolReference("a"), new Sy }) .matches( project( - ImmutableMap.of("projectedA", new ExpressionMatcher("a"), "projectedC", new ExpressionMatcher("a + b")), + ImmutableMap.of("projectedA", expression("a"), "projectedC", expression("a + b")), topN(1, ImmutableList.of(sort("a", ASCENDING, FIRST)), values("a", "b")))); } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestRemoveTrivialFilters.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestRemoveTrivialFilters.java index e415cb53e384..c641d37a3a41 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestRemoveTrivialFilters.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestRemoveTrivialFilters.java @@ -57,10 +57,12 @@ public void testRemovesNullFilter() { tester().assertThat(new RemoveTrivialFilters()) .on(p -> p.filter( - expression("null"), + expression("CAST(null AS boolean)"), p.values( ImmutableList.of(p.symbol("a")), ImmutableList.of(expressions("1"))))) - .matches(values("a")); + .matches(values( + ImmutableList.of("a"), + ImmutableList.of())); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java index 8d87edc0a274..adca98d2ccb7 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java @@ -16,11 +16,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.trino.sql.planner.Symbol; -import io.trino.sql.planner.assertions.ExpressionMatcher; import io.trino.sql.planner.iterative.rule.test.BaseRuleTest; import io.trino.sql.tree.ComparisonExpression; import org.testng.annotations.Test; +import static io.trino.sql.planner.assertions.PlanMatchPattern.expression; import static io.trino.sql.planner.assertions.PlanMatchPattern.join; import static io.trino.sql.planner.assertions.PlanMatchPattern.project; import static io.trino.sql.planner.assertions.PlanMatchPattern.values; @@ -120,8 +120,8 @@ public void testRewriteRightCorrelatedJoin() .matches( project( ImmutableMap.of( - "a", new ExpressionMatcher("if(b > a, a, null)"), - "b", new ExpressionMatcher("b")), + "a", expression("if(b > a, a, null)"), + "b", expression("b")), join(Type.INNER, builder -> builder .left(values("a")) .right(values("b"))))); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestDeterminePartitionCount.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestDeterminePartitionCount.java new file mode 100644 index 000000000000..8da217c0695d --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestDeterminePartitionCount.java @@ -0,0 +1,390 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.sql.planner.optimizations; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.units.DataSize; +import io.trino.Session; +import io.trino.connector.MockConnectorColumnHandle; +import io.trino.connector.MockConnectorFactory; +import io.trino.connector.MockConnectorTableHandle; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.statistics.ColumnStatistics; +import io.trino.spi.statistics.Estimate; +import io.trino.spi.statistics.TableStatistics; +import io.trino.sql.planner.assertions.BasePlanTest; +import io.trino.sql.planner.plan.AggregationNode; +import io.trino.sql.planner.plan.FilterNode; +import io.trino.sql.planner.plan.TableScanNode; +import io.trino.testing.LocalQueryRunner; +import org.intellij.lang.annotations.Language; +import org.testng.annotations.Test; + +import java.util.Optional; + +import static io.airlift.units.DataSize.Unit.MEGABYTE; +import static io.trino.SystemSessionProperties.MAX_HASH_PARTITION_COUNT; +import static io.trino.SystemSessionProperties.MIN_HASH_PARTITION_COUNT; +import static io.trino.SystemSessionProperties.MIN_INPUT_ROWS_PER_TASK; +import static io.trino.SystemSessionProperties.MIN_INPUT_SIZE_PER_TASK; +import static io.trino.spi.statistics.TableStatistics.empty; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static io.trino.sql.planner.assertions.PlanMatchPattern.exchange; +import static io.trino.sql.planner.assertions.PlanMatchPattern.join; +import static io.trino.sql.planner.assertions.PlanMatchPattern.node; +import static io.trino.sql.planner.assertions.PlanMatchPattern.output; +import static io.trino.sql.planner.assertions.PlanMatchPattern.project; +import static io.trino.sql.planner.assertions.PlanMatchPattern.tableScan; +import static io.trino.sql.planner.plan.ExchangeNode.Scope.LOCAL; +import static io.trino.sql.planner.plan.ExchangeNode.Scope.REMOTE; +import static io.trino.sql.planner.plan.JoinNode.Type.INNER; +import static io.trino.testing.TestingSession.testSessionBuilder; + +public class TestDeterminePartitionCount + extends BasePlanTest +{ + @Override + protected LocalQueryRunner createLocalQueryRunner() + { + String catalogName = "mock"; + MockConnectorFactory connectorFactory = MockConnectorFactory.builder() + .withGetTableHandle(((session, tableName) -> { + if (tableName.getTableName().equals("table_with_stats_a") + || tableName.getTableName().equals("table_with_stats_b") + || tableName.getTableName().equals("table_without_stats_a") + || tableName.getTableName().equals("table_without_stats_b")) { + return new MockConnectorTableHandle(tableName); + } + return null; + })) + .withGetColumns(schemaTableName -> ImmutableList.of( + new ColumnMetadata("column_a", VARCHAR), + new ColumnMetadata("column_b", VARCHAR))) + .withGetTableStatistics(tableName -> { + if (tableName.getTableName().equals("table_with_stats_a") + || tableName.getTableName().equals("table_with_stats_b")) { + return new TableStatistics( + Estimate.of(200), + ImmutableMap.of( + new MockConnectorColumnHandle("column_a", VARCHAR), + new ColumnStatistics(Estimate.of(0), Estimate.of(10000), Estimate.of(DataSize.of(100, MEGABYTE).toBytes()), Optional.empty()), + new MockConnectorColumnHandle("column_b", VARCHAR), + new ColumnStatistics(Estimate.of(0), Estimate.of(10000), Estimate.of(DataSize.of(100, MEGABYTE).toBytes()), Optional.empty()))); + } + return empty(); + }) + .withName(catalogName) + .build(); + + Session session = testSessionBuilder() + .setCatalog(catalogName) + .setSchema("default") + .build(); + LocalQueryRunner queryRunner = LocalQueryRunner.create(session); + queryRunner.createCatalog( + catalogName, + connectorFactory, + ImmutableMap.of()); + return queryRunner; + } + + @Test + public void testPlanWhenTableStatisticsArePresent() + { + @Language("SQL") String query = """ + SELECT count(column_a) FROM table_with_stats_a + """; + + // DeterminePartitionCount optimizer rule should fire and set the partitionCount to 5 for remote exchanges + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "10") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "4") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "400") + .build(), + output( + node(AggregationNode.class, + exchange(LOCAL, + exchange(REMOTE, Optional.of(5), + node(AggregationNode.class, + node(TableScanNode.class))))))); + } + + @Test + public void testPlanWhenTableStatisticsAreAbsent() + { + @Language("SQL") String query = """ + SELECT * FROM table_without_stats_a as a JOIN table_without_stats_b as b ON a.column_a = b.column_a + """; + + // DeterminePartitionCount optimizer rule should not fire and partitionCount will remain empty for remote exchanges + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "10") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "4") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "400") + .build(), + output( + join(INNER, builder -> builder + .equiCriteria("column_a", "column_a_0") + .right(exchange(LOCAL, + exchange(REMOTE, Optional.empty(), + project( + tableScan("table_without_stats_b", ImmutableMap.of("column_a_0", "column_a", "column_b_1", "column_b")))))) + .left(exchange(REMOTE, Optional.empty(), + project( + node(FilterNode.class, + tableScan("table_without_stats_a", ImmutableMap.of("column_a", "column_a", "column_b", "column_b"))))))))); + } + + @Test + public void testPlanWhenCrossJoinIsPresent() + { + @Language("SQL") String query = """ + SELECT * FROM table_with_stats_a CROSS JOIN table_with_stats_b + """; + + // DeterminePartitionCount optimizer rule should not fire and partitionCount will remain empty for remote exchanges + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "10") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "4") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "400") + .build(), + output( + join(INNER, builder -> builder + .right(exchange(LOCAL, + exchange(REMOTE, Optional.empty(), + tableScan("table_with_stats_b", ImmutableMap.of("column_a_0", "column_a", "column_b_1", "column_b"))))) + .left(tableScan("table_with_stats_a", ImmutableMap.of("column_a", "column_a", "column_b", "column_b")))))); + } + + @Test + public void testPlanWhenCrossJoinIsScalar() + { + @Language("SQL") String query = """ + SELECT * FROM table_with_stats_a CROSS JOIN (select max(column_a) from table_with_stats_b) t(a) + """; + + // DeterminePartitionCount optimizer rule should fire and set the partitionCount to 1 for remote exchanges + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "20") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "4") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "400") + .build(), + output( + join(INNER, builder -> builder + .right( + exchange(LOCAL, + exchange(REMOTE, Optional.of(15), + node(AggregationNode.class, + exchange(LOCAL, + exchange(REMOTE, Optional.of(15), + node(AggregationNode.class, + node(TableScanNode.class)))))))) + .left(node(TableScanNode.class))))); + } + + @Test + public void testPlanWhenJoinNodeStatsAreAbsent() + { + @Language("SQL") String query = """ + SELECT * FROM table_with_stats_a as a JOIN table_with_stats_b as b ON a.column_b = b.column_b + """; + + // DeterminePartitionCount optimizer rule should not fire and partitionCount will remain empty for remote exchanges + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "10") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "4") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "400") + .build(), + output( + join(INNER, builder -> builder + .equiCriteria("column_b", "column_b_1") + .right(exchange(LOCAL, + exchange(REMOTE, Optional.empty(), + project( + tableScan("table_with_stats_b", ImmutableMap.of("column_a_0", "column_a", "column_b_1", "column_b")))))) + .left(exchange(REMOTE, Optional.empty(), + project( + node(FilterNode.class, + tableScan("table_with_stats_a", ImmutableMap.of("column_a", "column_a", "column_b", "column_b"))))))))); + } + + @Test + public void testPlanWhenJoinNodeOutputIsBiggerThanRowsScanned() + { + @Language("SQL") String query = """ + SELECT a.column_a FROM table_with_stats_a as a JOIN table_with_stats_b as b ON a.column_a = b.column_a + """; + + // DeterminePartitionCount optimizer rule should fire and set the partitionCount to 20 for remote exchanges + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "50") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "4") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "400") + .build(), + output( + join(INNER, builder -> builder + .equiCriteria("column_a", "column_a_0") + .right(exchange(LOCAL, + // partition count should be more than 5 because of the presence of expanding join operation + exchange(REMOTE, Optional.of(10), + project( + tableScan("table_with_stats_b", ImmutableMap.of("column_a_0", "column_a")))))) + .left(exchange(REMOTE, Optional.of(10), + project( + node(FilterNode.class, + tableScan("table_with_stats_a", ImmutableMap.of("column_a", "column_a"))))))))); + } + + @Test + public void testEstimatedPartitionCountShouldNotBeGreaterThanMaxLimit() + { + @Language("SQL") String query = """ + SELECT * FROM table_with_stats_a as a JOIN table_with_stats_b as b ON a.column_a = b.column_a + """; + + // DeterminePartitionCount optimizer rule should not fire and partitionCount will remain empty for remote exchanges + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "5") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "2") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "400") + .build(), + output( + join(INNER, builder -> builder + .equiCriteria("column_a", "column_a_0") + .right(exchange(LOCAL, + exchange(REMOTE, Optional.empty(), + project( + tableScan("table_with_stats_b", ImmutableMap.of("column_a_0", "column_a", "column_b_1", "column_b")))))) + .left(exchange(REMOTE, Optional.empty(), + project( + node(FilterNode.class, + tableScan("table_with_stats_a", ImmutableMap.of("column_a", "column_a", "column_b", "column_b"))))))))); + } + + @Test + public void testEstimatedPartitionCountShouldNotBeLessThanMinLimit() + { + @Language("SQL") String query = """ + SELECT a.column_a FROM table_with_stats_a as a JOIN table_with_stats_b as b ON a.column_a = b.column_a + """; + + // DeterminePartitionCount optimizer rule estimate the partition count to 10 but because min limit is 15, it will set it to 15 + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "20") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "15") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "400") + .build(), + output( + join(INNER, builder -> builder + .equiCriteria("column_a", "column_a_0") + .right(exchange(LOCAL, + exchange(REMOTE, Optional.of(15), + project( + tableScan("table_with_stats_b", ImmutableMap.of("column_a_0", "column_a")))))) + .left(exchange(REMOTE, Optional.of(15), + project( + node(FilterNode.class, + tableScan("table_with_stats_a", ImmutableMap.of("column_a", "column_a"))))))))); + } + + @Test + public void testPlanWhenUnionNodeOutputIsBiggerThanJoinOutput() + { + @Language("SQL") String query = """ + SELECT a.column_b + FROM table_with_stats_a as a + JOIN table_with_stats_b as b + ON a.column_a = b.column_a + UNION ALL + SELECT column_b + FROM table_with_stats_b + """; + + // DeterminePartitionCount optimizer rule should fire and set the partitionCount to 10 for remote exchanges + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "50") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "4") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "400") + .build(), + output( + // partition count should be 15 with just join node but since we also have union, it should be 20 + exchange(REMOTE, Optional.of(20), + join(INNER, builder -> builder + .equiCriteria("column_a", "column_a_1") + .right(exchange(LOCAL, + // partition count should be 15 with just join node but since we also have union, it should be 20 + exchange(REMOTE, Optional.of(20), + project( + tableScan("table_with_stats_b", ImmutableMap.of("column_a_1", "column_a")))))) + // partition count should be 15 with just join node but since we also have union, it should be 20 + .left(exchange(REMOTE, Optional.of(20), + project( + node(FilterNode.class, + tableScan("table_with_stats_a", ImmutableMap.of("column_a", "column_a", "column_b_0", "column_b"))))))), + tableScan("table_with_stats_b", ImmutableMap.of("column_b_4", "column_b"))))); + } + + @Test + public void testPlanWhenEstimatedPartitionCountBasedOnRowsIsMoreThanOutputSize() + { + @Language("SQL") String query = """ + SELECT count(column_a) FROM table_with_stats_a + """; + + // DeterminePartitionCount optimizer rule should fire and set the partitionCount to 10 for remote exchanges + // based on rows count + assertDistributedPlan( + query, + Session.builder(getQueryRunner().getDefaultSession()) + .setSystemProperty(MAX_HASH_PARTITION_COUNT, "100") + .setSystemProperty(MIN_HASH_PARTITION_COUNT, "4") + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "20MB") + .setSystemProperty(MIN_INPUT_ROWS_PER_TASK, "20") + .build(), + output( + node(AggregationNode.class, + exchange(LOCAL, + exchange(REMOTE, Optional.of(10), + node(AggregationNode.class, + node(TableScanNode.class))))))); + } +} diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestCheckConstraint.java b/core/trino-main/src/test/java/io/trino/sql/query/TestCheckConstraint.java new file mode 100644 index 000000000000..b9fe3b011c6a --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestCheckConstraint.java @@ -0,0 +1,439 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.sql.query; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.Session; +import io.trino.connector.MockConnectorFactory; +import io.trino.plugin.tpch.TpchConnectorFactory; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.security.Identity; +import io.trino.testing.LocalQueryRunner; +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 static io.trino.connector.MockConnectorEntities.TPCH_NATION_DATA; +import static io.trino.connector.MockConnectorEntities.TPCH_NATION_SCHEMA; +import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; +import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; + +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) +public class TestCheckConstraint +{ + private static final String LOCAL_CATALOG = "local"; + private static final String MOCK_CATALOG = "mock"; + private static final String USER = "user"; + + private static final Session SESSION = testSessionBuilder() + .setCatalog(LOCAL_CATALOG) + .setSchema(TINY_SCHEMA_NAME) + .setIdentity(Identity.forUser(USER).build()) + .build(); + + private QueryAssertions assertions; + + @BeforeAll + public void init() + { + LocalQueryRunner runner = LocalQueryRunner.builder(SESSION).build(); + + runner.createCatalog(LOCAL_CATALOG, new TpchConnectorFactory(1), ImmutableMap.of()); + + MockConnectorFactory mock = MockConnectorFactory.builder() + .withGetColumns(schemaTableName -> { + if (schemaTableName.equals(new SchemaTableName("tiny", "nation"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_multiple_column_constraint"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_invalid_function"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_not_boolean_expression"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_subquery"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_current_date"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_current_time"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_current_timestamp"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_localtime"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_localtimestamp"))) { + return TPCH_NATION_SCHEMA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_not_deterministic"))) { + return TPCH_NATION_SCHEMA; + } + throw new UnsupportedOperationException(); + }) + .withCheckConstraints(schemaTableName -> { + if (schemaTableName.equals(new SchemaTableName("tiny", "nation"))) { + return ImmutableList.of("regionkey < 10"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_multiple_column_constraint"))) { + return ImmutableList.of("nationkey > 100 AND regionkey > 50"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_invalid_function"))) { + return ImmutableList.of("invalid_function(nationkey) > 100"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_not_boolean_expression"))) { + return ImmutableList.of("1 + 1"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_subquery"))) { + return ImmutableList.of("nationkey > (SELECT count(*) FROM nation)"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_current_date"))) { + return ImmutableList.of("CURRENT_DATE > DATE '2022-12-31'"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_current_time"))) { + return ImmutableList.of("CURRENT_TIME > TIME '12:34:56.123+00:00'"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_current_timestamp"))) { + return ImmutableList.of("CURRENT_TIMESTAMP > TIMESTAMP '2022-12-31 23:59:59'"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_localtime"))) { + return ImmutableList.of("LOCALTIME > TIME '12:34:56.123'"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_localtimestamp"))) { + return ImmutableList.of("LOCALTIMESTAMP > TIMESTAMP '2022-12-31 23:59:59'"); + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_not_deterministic"))) { + return ImmutableList.of("nationkey > random()"); + } + throw new UnsupportedOperationException(); + }) + .withData(schemaTableName -> { + if (schemaTableName.equals(new SchemaTableName("tiny", "nation"))) { + return TPCH_NATION_DATA; + } + if (schemaTableName.equals(new SchemaTableName("tiny", "nation_multiple_column_constraint"))) { + return TPCH_NATION_DATA; + } + throw new UnsupportedOperationException(); + }) + .build(); + + runner.createCatalog(MOCK_CATALOG, mock, ImmutableMap.of()); + + assertions = new QueryAssertions(runner); + } + + @AfterAll + public void teardown() + { + assertions.close(); + assertions = null; + } + + /** + * @see #testMergeInsert() + */ + @Test + public void testInsert() + { + assertThat(assertions.query("INSERT INTO mock.tiny.nation VALUES (101, 'POLAND', 0, 'No comment')")) + .matches("SELECT BIGINT '1'"); + + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation VALUES (26, 'POLAND', 11, 'No comment')")) + .hasMessage("Check constraint violation: (regionkey < 10)"); + assertThatThrownBy(() -> assertions.query(""" + INSERT INTO mock.tiny.nation VALUES + (26, 'POLAND', 11, 'No comment'), + (27, 'HOLLAND', 11, 'A comment') + """)) + .hasMessage("Check constraint violation: (regionkey < 10)"); + assertThatThrownBy(() -> assertions.query(""" + INSERT INTO mock.tiny.nation VALUES + (26, 'POLAND', 11, 'No comment'), + (27, 'HOLLAND', 11, 'A comment') + """)) + .hasMessage("Check constraint violation: (regionkey < 10)"); + } + + /** + * Like {@link #testInsert} but using the MERGE statement. + */ + @Test + public void testMergeInsert() + { + // Within allowed check constraint + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 42) t(dummy) ON false + WHEN NOT MATCHED THEN INSERT VALUES (101, 'POLAND', 0, 'No comment') + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + + // Outside allowed check constraint + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 42) t(dummy) ON false + WHEN NOT MATCHED THEN INSERT VALUES (26, 'POLAND', 0, 'No comment') + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES (26, 'POLAND', 0, 'No comment'), (27, 'HOLLAND', 0, 'A comment')) t(a,b,c,d) ON nationkey = a + WHEN NOT MATCHED THEN INSERT VALUES (a,b,c,d) + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 42) t(dummy) ON false + WHEN NOT MATCHED THEN INSERT (nationkey) VALUES (NULL) + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 42) t(dummy) ON false + WHEN NOT MATCHED THEN INSERT (nationkey) VALUES (0) + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + } + + @Test + public void testInsertAllowUnknown() + { + // Predicate evaluates to UNKNOWN (e.g. NULL > 100) should not violate check constraint + assertThat(assertions.query("INSERT INTO mock.tiny.nation(nationkey) VALUES (null)")) + .matches("SELECT BIGINT '1'"); + assertThat(assertions.query("INSERT INTO mock.tiny.nation(regionkey) VALUES (0)")) + .matches("SELECT BIGINT '1'"); + } + + @Test + public void testInsertCheckMultipleColumns() + { + assertThat(assertions.query("INSERT INTO mock.tiny.nation_multiple_column_constraint VALUES (101, 'POLAND', 51, 'No comment')")) + .matches("SELECT BIGINT '1'"); + + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_multiple_column_constraint VALUES (101, 'POLAND', 50, 'No comment')")) + .hasMessage("Check constraint violation: ((nationkey > 100) AND (regionkey > 50))"); + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_multiple_column_constraint VALUES (100, 'POLAND', 51, 'No comment')")) + .hasMessage("Check constraint violation: ((nationkey > 100) AND (regionkey > 50))"); + } + + @Test + public void testInsertSubquery() + { + assertThat(assertions.query("INSERT INTO mock.tiny.nation_subquery VALUES (26, 'POLAND', 51, 'No comment')")) + .matches("SELECT BIGINT '1'"); + + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_subquery VALUES (10, 'POLAND', 0, 'No comment')")) + .hasMessage("Check constraint violation: (nationkey > (SELECT count(*)\nFROM\n nation\n))"); + } + + @Test + public void testInsertUnsupportedCurrentDate() + { + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_current_date VALUES (101, 'POLAND', 0, 'No comment')")) + .hasMessageContaining("Check constraint expression should not contain temporal expression"); + } + + @Test + public void testInsertUnsupportedCurrentTime() + { + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_current_time VALUES (101, 'POLAND', 0, 'No comment')")) + .hasMessageContaining("Check constraint expression should not contain temporal expression"); + } + + @Test + public void testInsertUnsupportedCurrentTimestamp() + { + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_current_timestamp VALUES (101, 'POLAND', 0, 'No comment')")) + .hasMessageContaining("Check constraint expression should not contain temporal expression"); + } + + @Test + public void testInsertUnsupportedLocaltime() + { + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_localtime VALUES (101, 'POLAND', 0, 'No comment')")) + .hasMessageContaining("Check constraint expression should not contain temporal expression"); + } + + @Test + public void testInsertUnsupportedLocaltimestamp() + { + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_localtimestamp VALUES (101, 'POLAND', 0, 'No comment')")) + .hasMessageContaining("Check constraint expression should not contain temporal expression"); + } + + @Test + public void testInsertUnsupportedConstraint() + { + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_invalid_function VALUES (101, 'POLAND', 0, 'No comment')")) + .hasMessageContaining("Function 'invalid_function' not registered"); + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_not_boolean_expression VALUES (101, 'POLAND', 0, 'No comment')")) + .hasMessageContaining("to be of type BOOLEAN, but was integer"); + } + + @Test + public void testInsertNotDeterministic() + { + assertThatThrownBy(() -> assertions.query("INSERT INTO mock.tiny.nation_not_deterministic VALUES (100, 'POLAND', 0, 'No comment')")) + .hasMessageContaining("Check constraint expression should be deterministic"); + } + + /** + * @see #testMergeDelete() + */ + @Test + public void testDelete() + { + assertThat(assertions.query("DELETE FROM mock.tiny.nation WHERE nationkey < 3")) + .matches("SELECT BIGINT '3'"); + assertThat(assertions.query("DELETE FROM mock.tiny.nation WHERE nationkey IN (1, 2, 3)")) + .matches("SELECT BIGINT '3'"); + } + + /** + * Like {@link #testDelete()} but using the MERGE statement. + */ + @Test + public void testMergeDelete() + { + // Within allowed check constraint + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 1,2) t(x) ON nationkey = x + WHEN MATCHED THEN DELETE + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + + // Outside allowed check constraint + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 1,2,3,4,5) t(x) ON regionkey = x + WHEN MATCHED THEN DELETE + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 1,11) t(x) ON nationkey = x + WHEN MATCHED THEN DELETE + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 11,12,13,14,15) t(x) ON nationkey = x + WHEN MATCHED THEN DELETE + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + } + + /** + * @see #testMergeUpdate() + */ + @Test + public void testUpdate() + { + // Within allowed check constraint + assertThatThrownBy(() -> assertions.query("UPDATE mock.tiny.nation SET regionkey = regionkey * 2 WHERE nationkey < 3")) + .hasMessage("line 1:1: Updating a table with a check constraint is not supported"); + assertThatThrownBy(() -> assertions.query("UPDATE mock.tiny.nation SET regionkey = regionkey * 2 WHERE nationkey IN (1, 2, 3)")) + .hasMessage("line 1:1: Updating a table with a check constraint is not supported"); + + // Outside allowed check constraint + assertThatThrownBy(() -> assertions.query("UPDATE mock.tiny.nation SET regionkey = regionkey * 2")) + .hasMessage("line 1:1: Updating a table with a check constraint is not supported"); + assertThatThrownBy(() -> assertions.query("UPDATE mock.tiny.nation SET regionkey = regionkey * 2 WHERE nationkey IN (1, 11)")) + .hasMessage("line 1:1: Updating a table with a check constraint is not supported"); + + assertThatThrownBy(() -> assertions.query("UPDATE mock.tiny.nation SET regionkey = regionkey * 2 WHERE nationkey = 11")) + .hasMessage("line 1:1: Updating a table with a check constraint is not supported"); + + // Within allowed check constraint, but updated rows are outside the check constraint + assertThatThrownBy(() -> assertions.query("UPDATE mock.tiny.nation SET nationkey = 10 WHERE nationkey < 3")) + .hasMessage("line 1:1: Updating a table with a check constraint is not supported"); + assertThatThrownBy(() -> assertions.query("UPDATE mock.tiny.nation SET nationkey = null WHERE nationkey < 3")) + .hasMessage("line 1:1: Updating a table with a check constraint is not supported"); + + // Outside allowed check constraint, and updated rows are outside the check constraint + assertThatThrownBy(() -> assertions.query("UPDATE mock.tiny.nation SET nationkey = 10 WHERE nationkey = 10")) + .hasMessage("line 1:1: Updating a table with a check constraint is not supported"); + assertThatThrownBy(() -> assertions.query("UPDATE mock.tiny.nation SET nationkey = null WHERE nationkey = null ")) + .hasMessage("line 1:1: Updating a table with a check constraint is not supported"); + } + + /** + * Like {@link #testUpdate()} but using the MERGE statement. + */ + @Test + public void testMergeUpdate() + { + // Within allowed check constraint + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 5) t(x) ON nationkey = x + WHEN MATCHED THEN UPDATE SET regionkey = regionkey * 2 + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + + // Outside allowed check constraint + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 1,2,3,4,5,6) t(x) ON regionkey = x + WHEN MATCHED THEN UPDATE SET regionkey = regionkey * 2 + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 1, 11) t(x) ON nationkey = x + WHEN MATCHED THEN UPDATE SET regionkey = regionkey * 2 + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 11) t(x) ON nationkey = x + WHEN MATCHED THEN UPDATE SET regionkey = regionkey * 2 + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + + // Within allowed check constraint, but updated rows are outside the check constraint + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 1,2,3) t(x) ON nationkey = x + WHEN MATCHED THEN UPDATE SET nationkey = 10 + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 1,2,3) t(x) ON nationkey = x + WHEN MATCHED THEN UPDATE SET nationkey = NULL + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + + // Outside allowed check constraint, but updated rows are outside the check constraint + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 10) t(x) ON nationkey = x + WHEN MATCHED THEN UPDATE SET nationkey = 13 + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 10) t(x) ON nationkey = x + WHEN MATCHED THEN UPDATE SET nationkey = NULL + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + assertThatThrownBy(() -> assertions.query(""" + MERGE INTO mock.tiny.nation USING (VALUES 10) t(x) ON nationkey IS NULL + WHEN MATCHED THEN UPDATE SET nationkey = 13 + """)) + .hasMessage("line 1:1: Cannot merge into a table with check constraints"); + } +} diff --git a/core/trino-main/src/test/java/io/trino/type/TestTimeWithTimeZoneType.java b/core/trino-main/src/test/java/io/trino/type/TestTimeWithTimeZoneType.java index 042388c74b0e..33566ff6906c 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestTimeWithTimeZoneType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestTimeWithTimeZoneType.java @@ -20,30 +20,30 @@ import static io.trino.spi.type.DateTimeEncoding.packTimeWithTimeZone; import static io.trino.spi.type.DateTimeEncoding.unpackOffsetMinutes; import static io.trino.spi.type.DateTimeEncoding.unpackTimeNanos; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; public class TestTimeWithTimeZoneType extends AbstractTestType { public TestTimeWithTimeZoneType() { - super(TIME_WITH_TIME_ZONE, SqlTimeWithTimeZone.class, createTestBlock()); + super(TIME_TZ_MILLIS, SqlTimeWithTimeZone.class, createTestBlock()); } public static Block createTestBlock() { - BlockBuilder blockBuilder = TIME_WITH_TIME_ZONE.createBlockBuilder(null, 15); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(1_111_000_000L, 0)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(1_111_000_000L, 1)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(1_111_000_000L, 2)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 3)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 4)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 5)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 6)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 7)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(3_333_000_000L, 8)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(3_333_000_000L, 9)); - TIME_WITH_TIME_ZONE.writeLong(blockBuilder, packTimeWithTimeZone(4_444_000_000L, 10)); + BlockBuilder blockBuilder = TIME_TZ_MILLIS.createBlockBuilder(null, 15); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(1_111_000_000L, 0)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(1_111_000_000L, 1)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(1_111_000_000L, 2)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 3)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 4)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 5)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 6)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(2_222_000_000L, 7)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(3_333_000_000L, 8)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(3_333_000_000L, 9)); + TIME_TZ_MILLIS.writeLong(blockBuilder, packTimeWithTimeZone(4_444_000_000L, 10)); return blockBuilder.build(); } diff --git a/core/trino-main/src/test/java/io/trino/type/TestTypeCoercion.java b/core/trino-main/src/test/java/io/trino/type/TestTypeCoercion.java index 40708bbaf5c3..95987e41ad3d 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestTypeCoercion.java +++ b/core/trino-main/src/test/java/io/trino/type/TestTypeCoercion.java @@ -40,9 +40,13 @@ import static io.trino.spi.type.RowType.rowType; import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeType.createTimeType; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; +import static io.trino.spi.type.TimeWithTimeZoneType.createTimeWithTimeZoneType; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; +import static io.trino.spi.type.TimestampType.createTimestampType; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; +import static io.trino.spi.type.TimestampWithTimeZoneType.createTimestampWithTimeZoneType; import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.VARCHAR; @@ -124,9 +128,20 @@ public void testTypeCompatibility() assertThat(UNKNOWN, BIGINT).hasCommonSuperType(BIGINT).canCoerceFirstToSecondOnly(); assertThat(BIGINT, DOUBLE).hasCommonSuperType(DOUBLE).canCoerceFirstToSecondOnly(); + + // date / timestamp + assertThat(DATE, createTimestampType(0)).hasCommonSuperType(createTimestampType(0)); + assertThat(DATE, createTimestampType(2)).hasCommonSuperType(createTimestampType(2)); assertThat(DATE, TIMESTAMP_MILLIS).hasCommonSuperType(TIMESTAMP_MILLIS).canCoerceFirstToSecondOnly(); + assertThat(DATE, createTimestampType(7)).hasCommonSuperType(createTimestampType(7)); + + // date / timestamp with time zone + assertThat(DATE, createTimestampWithTimeZoneType(0)).hasCommonSuperType(createTimestampWithTimeZoneType(0)); + assertThat(DATE, createTimestampWithTimeZoneType(2)).hasCommonSuperType(createTimestampWithTimeZoneType(2)); assertThat(DATE, TIMESTAMP_TZ_MILLIS).hasCommonSuperType(TIMESTAMP_TZ_MILLIS).canCoerceFirstToSecondOnly(); - assertThat(TIME_MILLIS, TIME_WITH_TIME_ZONE).hasCommonSuperType(TIME_WITH_TIME_ZONE).canCoerceFirstToSecondOnly(); + assertThat(DATE, createTimestampWithTimeZoneType(7)).hasCommonSuperType(createTimestampWithTimeZoneType(7)); + + assertThat(TIME_MILLIS, TIME_TZ_MILLIS).hasCommonSuperType(TIME_TZ_MILLIS).canCoerceFirstToSecondOnly(); assertThat(TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS).hasCommonSuperType(TIMESTAMP_TZ_MILLIS).canCoerceFirstToSecondOnly(); assertThat(VARCHAR, JONI_REGEXP).hasCommonSuperType(JONI_REGEXP).canCoerceFirstToSecondOnly(); assertThat(VARCHAR, re2jType).hasCommonSuperType(re2jType).canCoerceFirstToSecondOnly(); @@ -138,7 +153,7 @@ public void testTypeCompatibility() assertThat(REAL, INTEGER).hasCommonSuperType(REAL).canCoerceSecondToFirstOnly(); assertThat(REAL, BIGINT).hasCommonSuperType(REAL).canCoerceSecondToFirstOnly(); - assertThat(TIMESTAMP_MILLIS, TIME_WITH_TIME_ZONE).isIncompatible(); + assertThat(TIMESTAMP_MILLIS, TIME_TZ_MILLIS).isIncompatible(); assertThat(VARBINARY, VARCHAR).isIncompatible(); assertThat(UNKNOWN, new ArrayType(BIGINT)).hasCommonSuperType(new ArrayType(BIGINT)).canCoerceFirstToSecondOnly(); @@ -147,6 +162,20 @@ public void testTypeCompatibility() assertThat(mapType(BIGINT, DOUBLE), mapType(BIGINT, DOUBLE)).hasCommonSuperType(mapType(BIGINT, DOUBLE)).canCoerceToEachOther(); assertThat(mapType(BIGINT, DOUBLE), mapType(DOUBLE, DOUBLE)).hasCommonSuperType(mapType(DOUBLE, DOUBLE)).canCoerceFirstToSecondOnly(); + // time / time + assertThat(createTimeType(5), createTimeType(9)).hasCommonSuperType(createTimeType(9)); + assertThat(createTimeType(9), createTimeType(5)).hasCommonSuperType(createTimeType(9)); + + // time / time with time zone + assertThat(createTimeType(5), createTimeWithTimeZoneType(9)).hasCommonSuperType(createTimeWithTimeZoneType(9)); + assertThat(createTimeType(9), createTimeWithTimeZoneType(5)).hasCommonSuperType(createTimeWithTimeZoneType(9)); + assertThat(createTimeWithTimeZoneType(5), createTimeType(9)).hasCommonSuperType(createTimeWithTimeZoneType(9)); + assertThat(createTimeWithTimeZoneType(9), createTimeType(5)).hasCommonSuperType(createTimeWithTimeZoneType(9)); + + // time with time zone / time with time zone + assertThat(createTimeWithTimeZoneType(5), createTimeWithTimeZoneType(9)).hasCommonSuperType(createTimeWithTimeZoneType(9)); + assertThat(createTimeWithTimeZoneType(9), createTimeWithTimeZoneType(5)).hasCommonSuperType(createTimeWithTimeZoneType(9)); + assertThat( rowType(field("a", BIGINT), field("b", DOUBLE), field("c", VARCHAR)), rowType(field("a", BIGINT), field("b", DOUBLE), field("c", VARCHAR))) diff --git a/core/trino-parser/pom.xml b/core/trino-parser/pom.xml index f40a79f49193..95b831a01e5b 100644 --- a/core/trino-parser/pom.xml +++ b/core/trino-parser/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-main/pom.xml b/core/trino-server-main/pom.xml index f069443b6b88..5b98525dd571 100644 --- a/core/trino-server-main/pom.xml +++ b/core/trino-server-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index 704b87453760..a344d98e47fc 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/core/trino-server/pom.xml b/core/trino-server/pom.xml index 1e26b6ff98ef..4af3bde712ce 100644 --- a/core/trino-server/pom.xml +++ b/core/trino-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 94ac90ad6499..7451d7f8eefb 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml @@ -219,62 +219,6 @@ - - true - java.method.removed - method boolean io.trino.spi.connector.MaterializedViewFreshness::isMaterializedViewFresh() - - - true - java.method.visibilityReduced - method java.lang.invoke.MethodHandle io.trino.spi.predicate.Range::getComparisonOperator(io.trino.spi.type.Type) - method java.lang.invoke.MethodHandle io.trino.spi.predicate.Range::getComparisonOperator(io.trino.spi.type.Type) - protected - package - It was not accessible outside of SPI anyway - - - true - java.method.parameterTypeChanged - parameter void io.trino.spi.eventlistener.ColumnInfo::<init>(java.lang.String, ===java.util.List<java.lang.String>===) - parameter void io.trino.spi.eventlistener.ColumnInfo::<init>(java.lang.String, ===java.util.Optional<java.lang.String>===) - 1 - Removing support for multiple masks on a given column, as they are error prone - - - true - java.method.removed - method java.util.List<java.lang.String> io.trino.spi.eventlistener.ColumnInfo::getMasks() - Removing support for multiple masks on a given column, as they are error prone - - - java.method.removed - method io.trino.spi.connector.ConnectorTableHandle io.trino.spi.connector.ConnectorMetadata::beginDelete(io.trino.spi.connector.ConnectorSession, io.trino.spi.connector.ConnectorTableHandle, io.trino.spi.connector.RetryMode) - - - java.method.removed - method io.trino.spi.connector.ConnectorTableHandle io.trino.spi.connector.ConnectorMetadata::beginUpdate(io.trino.spi.connector.ConnectorSession, io.trino.spi.connector.ConnectorTableHandle, java.util.List<io.trino.spi.connector.ColumnHandle>, io.trino.spi.connector.RetryMode) - - - java.method.removed - method void io.trino.spi.connector.ConnectorMetadata::finishDelete(io.trino.spi.connector.ConnectorSession, io.trino.spi.connector.ConnectorTableHandle, java.util.Collection<io.airlift.slice.Slice>) - - - java.method.removed - method void io.trino.spi.connector.ConnectorMetadata::finishUpdate(io.trino.spi.connector.ConnectorSession, io.trino.spi.connector.ConnectorTableHandle, java.util.Collection<io.airlift.slice.Slice>) - - - java.method.removed - method io.trino.spi.connector.ColumnHandle io.trino.spi.connector.ConnectorMetadata::getDeleteRowIdColumnHandle(io.trino.spi.connector.ConnectorSession, io.trino.spi.connector.ConnectorTableHandle) - - - java.method.removed - method io.trino.spi.connector.ColumnHandle io.trino.spi.connector.ConnectorMetadata::getUpdateRowIdColumnHandle(io.trino.spi.connector.ConnectorSession, io.trino.spi.connector.ConnectorTableHandle, java.util.List<io.trino.spi.connector.ColumnHandle>) - - - java.class.removed - interface io.trino.spi.connector.UpdatablePageSource - diff --git a/core/trino-spi/src/main/java/io/trino/spi/StandardErrorCode.java b/core/trino-spi/src/main/java/io/trino/spi/StandardErrorCode.java index cf5892e56ca9..f40589aab433 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/StandardErrorCode.java +++ b/core/trino-spi/src/main/java/io/trino/spi/StandardErrorCode.java @@ -144,6 +144,7 @@ public enum StandardErrorCode INVALID_COPARTITIONING(120, USER_ERROR), INVALID_TABLE_FUNCTION_INVOCATION(121, USER_ERROR), DUPLICATE_RANGE_VARIABLE(122, USER_ERROR), + INVALID_CHECK_CONSTRAINT(123, USER_ERROR), GENERIC_INTERNAL_ERROR(65536, INTERNAL_ERROR), TOO_MANY_REQUESTS_FAILED(65537, INTERNAL_ERROR), diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockEncoding.java b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockEncoding.java index fea15b7aec70..dcb137eacbf7 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockEncoding.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockEncoding.java @@ -42,14 +42,22 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO int positionCount = variableWidthBlock.getPositionCount(); sliceOutput.appendInt(positionCount); - // offsets + // lengths + int[] lengths = new int[positionCount]; int totalLength = 0; + int nonNullsCount = 0; + for (int position = 0; position < positionCount; position++) { int length = variableWidthBlock.getSliceLength(position); totalLength += length; - sliceOutput.appendInt(totalLength); + lengths[nonNullsCount] = length; + nonNullsCount += variableWidthBlock.isNull(position) ? 0 : 1; } + sliceOutput + .appendInt(nonNullsCount) + .writeBytes(Slices.wrappedIntArray(lengths, 0, nonNullsCount)); + encodeNullsAsBits(sliceOutput, variableWidthBlock); sliceOutput @@ -61,9 +69,11 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO public Block readBlock(BlockEncodingSerde blockEncodingSerde, SliceInput sliceInput) { int positionCount = sliceInput.readInt(); + int nonNullsCount = sliceInput.readInt(); int[] offsets = new int[positionCount + 1]; - sliceInput.readBytes(Slices.wrappedIntArray(offsets), SIZE_OF_INT, positionCount * SIZE_OF_INT); + int[] lengths = new int[nonNullsCount]; + sliceInput.readBytes(Slices.wrappedIntArray(lengths), 0, nonNullsCount * SIZE_OF_INT); boolean[] valueIsNull = decodeNullBits(sliceInput, positionCount).orElse(null); @@ -71,6 +81,16 @@ public Block readBlock(BlockEncodingSerde blockEncodingSerde, SliceInput sliceIn Slice slice = Slices.allocate(blockSize); sliceInput.readBytes(slice); + int nonNullPosition = 0; + int offset = 0; + + for (int i = 0; i < positionCount; i++) { + if (valueIsNull == null || !valueIsNull[i]) { + offset += lengths[nonNullPosition]; + nonNullPosition++; + } + offsets[i + 1] = offset; + } return new VariableWidthBlock(0, positionCount, slice, offsets, valueIsNull); } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java index 26041fe75aea..288fc373da67 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java @@ -21,6 +21,11 @@ public interface ConnectorContext { + default CatalogHandle getCatalogHandle() + { + throw new UnsupportedOperationException(); + } + default NodeManager getNodeManager() { throw new UnsupportedOperationException(); diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorTableMetadata.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorTableMetadata.java index cb1963095098..2de63797c2c2 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorTableMetadata.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorTableMetadata.java @@ -13,6 +13,8 @@ */ package io.trino.spi.connector; +import io.trino.spi.Experimental; + import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -29,6 +31,7 @@ public class ConnectorTableMetadata private final Optional comment; private final List columns; private final Map properties; + private final List checkConstraints; public ConnectorTableMetadata(SchemaTableName table, List columns) { @@ -37,19 +40,27 @@ public ConnectorTableMetadata(SchemaTableName table, List column public ConnectorTableMetadata(SchemaTableName table, List columns, Map properties) { - this(table, columns, properties, Optional.empty()); + this(table, columns, properties, Optional.empty(), List.of()); } public ConnectorTableMetadata(SchemaTableName table, List columns, Map properties, Optional comment) + { + this(table, columns, properties, comment, List.of()); + } + + @Experimental(eta = "2023-03-31") + public ConnectorTableMetadata(SchemaTableName table, List columns, Map properties, Optional comment, List checkConstraints) { requireNonNull(table, "table is null"); requireNonNull(columns, "columns is null"); requireNonNull(comment, "comment is null"); + requireNonNull(checkConstraints, "checkConstraints is null"); this.table = table; this.columns = List.copyOf(columns); this.properties = Collections.unmodifiableMap(new LinkedHashMap<>(properties)); this.comment = comment; + this.checkConstraints = List.copyOf(checkConstraints); } public SchemaTableName getTable() @@ -72,13 +83,27 @@ public Optional getComment() return comment; } + /** + * List of constraints data in a table is expected to satisfy. + * Engine ensures rows written to a table meet these constraints. + * A check constraint is satisfied when it evaluates to True or Unknown. + * + * @return List of string representation of a Trino SQL scalar expression that can refer to table columns by name and produces a result coercible to boolean + */ + @Experimental(eta = "2023-03-31") + public List getCheckConstraints() + { + return checkConstraints; + } + public ConnectorTableSchema getTableSchema() { return new ConnectorTableSchema( table, columns.stream() .map(ColumnMetadata::getColumnSchema) - .collect(toUnmodifiableList())); + .collect(toUnmodifiableList()), + checkConstraints); } @Override @@ -89,6 +114,7 @@ public String toString() sb.append(", columns=").append(columns); sb.append(", properties=").append(properties); comment.ifPresent(value -> sb.append(", comment='").append(value).append("'")); + sb.append(", checkConstraints=").append(checkConstraints); sb.append('}'); return sb.toString(); } diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorTableSchema.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorTableSchema.java index afa9694ecb66..186f1c733a0c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorTableSchema.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorTableSchema.java @@ -13,6 +13,8 @@ */ package io.trino.spi.connector; +import io.trino.spi.Experimental; + import java.util.List; import static java.util.Objects.requireNonNull; @@ -21,14 +23,23 @@ public class ConnectorTableSchema { private final SchemaTableName table; private final List columns; + private final List checkConstraints; public ConnectorTableSchema(SchemaTableName table, List columns) + { + this(table, columns, List.of()); + } + + @Experimental(eta = "2023-03-31") + public ConnectorTableSchema(SchemaTableName table, List columns, List checkConstraints) { requireNonNull(table, "table is null"); requireNonNull(columns, "columns is null"); + requireNonNull(checkConstraints, "checkConstraints is null"); this.table = table; this.columns = List.copyOf(columns); + this.checkConstraints = List.copyOf(checkConstraints); } public SchemaTableName getTable() @@ -41,12 +52,26 @@ public List getColumns() return columns; } + /** + * List of constraints data in a table is expected to satisfy. + * Engine ensures rows written to a table meet these constraints. + * A check constraint is satisfied when it evaluates to True or Unknown. + * + * @return List of string representation of a Trino SQL scalar expression that can refer to table columns by name and produces a result coercible to boolean + */ + @Experimental(eta = "2023-03-31") + public List getCheckConstraints() + { + return checkConstraints; + } + @Override public String toString() { return new StringBuilder("ConnectorTableSchema{") .append("table=").append(table) .append(", columns=").append(columns) + .append(", checkConstraints=").append(checkConstraints) .append('}') .toString(); } diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/MaterializedViewFreshness.java b/core/trino-spi/src/main/java/io/trino/spi/connector/MaterializedViewFreshness.java index f9d2177aa26a..ce120d660b21 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/MaterializedViewFreshness.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/MaterializedViewFreshness.java @@ -16,20 +16,12 @@ import java.util.Objects; import java.util.StringJoiner; -import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.FRESH; -import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.STALE; import static java.util.Objects.requireNonNull; public final class MaterializedViewFreshness { private final Freshness freshness; - @Deprecated - public MaterializedViewFreshness(boolean materializedViewFresh) - { - this(materializedViewFresh ? FRESH : STALE); - } - public MaterializedViewFreshness(Freshness freshness) { this.freshness = requireNonNull(freshness, "freshness is null"); diff --git a/core/trino-spi/src/main/java/io/trino/spi/predicate/TupleDomain.java b/core/trino-spi/src/main/java/io/trino/spi/predicate/TupleDomain.java index f93c499f1d64..e4ab997b45c4 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/predicate/TupleDomain.java +++ b/core/trino-spi/src/main/java/io/trino/spi/predicate/TupleDomain.java @@ -414,11 +414,7 @@ public static TupleDomain columnWiseUnion(List> tupleDomai if (!domain.isNone()) { for (Map.Entry entry : domain.getDomains().get().entrySet()) { if (commonColumns.contains(entry.getKey())) { - List domainForColumn = domainsByColumn.get(entry.getKey()); - if (domainForColumn == null) { - domainForColumn = new ArrayList<>(); - domainsByColumn.put(entry.getKey(), domainForColumn); - } + List domainForColumn = domainsByColumn.computeIfAbsent(entry.getKey(), ignored -> new ArrayList<>()); domainForColumn.add(entry.getValue()); } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/security/AccessDeniedException.java b/core/trino-spi/src/main/java/io/trino/spi/security/AccessDeniedException.java index 598849236d92..fd8d782bfd69 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/security/AccessDeniedException.java +++ b/core/trino-spi/src/main/java/io/trino/spi/security/AccessDeniedException.java @@ -14,9 +14,11 @@ package io.trino.spi.security; import io.trino.spi.TrinoException; +import io.trino.spi.function.FunctionKind; import java.security.Principal; import java.util.Collection; +import java.util.Locale; import java.util.Optional; import java.util.Set; @@ -273,6 +275,11 @@ public static void denyShowColumns(String tableName) throw new AccessDeniedException(format("Cannot show columns of table %s", tableName)); } + public static void denyShowColumns(String tableName, String extraInfo) + { + throw new AccessDeniedException(format("Cannot show columns of table %s%s", tableName, formatExtraInfo(extraInfo))); + } + public static void denyAddColumn(String tableName) { denyAddColumn(tableName, null); @@ -653,6 +660,11 @@ public static void denyExecuteFunction(String functionName) throw new AccessDeniedException(format("Cannot execute function %s", functionName)); } + public static void denyExecuteFunction(String functionName, FunctionKind functionKind, String extraInfo) + { + throw new AccessDeniedException(format("Cannot execute %s function %s%s", functionKind.name().toLowerCase(Locale.ROOT), functionName, formatExtraInfo(extraInfo))); + } + public static void denyExecuteTableProcedure(String tableName, String procedureName) { throw new AccessDeniedException(format("Cannot execute table procedure %s on %s", procedureName, tableName)); diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneParametricType.java b/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneParametricType.java index 8f12fc6065e7..701bfaab1eb5 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneParametricType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneParametricType.java @@ -30,7 +30,7 @@ public String getName() public Type createType(TypeManager typeManager, List parameters) { if (parameters.isEmpty()) { - return TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; + return TimeWithTimeZoneType.TIME_TZ_MILLIS; } if (parameters.size() != 1) { throw new IllegalArgumentException("Expected exactly one parameter for TIME WITH TIME ZONE"); diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneType.java b/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneType.java index 0b27040581ef..e3d0fbc706c6 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneType.java @@ -28,30 +28,35 @@ public abstract sealed class TimeWithTimeZoneType public static final int DEFAULT_PRECISION = 3; // TODO: should be 6 per SQL spec + private static final TimeWithTimeZoneType[] TYPES = new TimeWithTimeZoneType[MAX_PRECISION + 1]; + + static { + for (int precision = 0; precision <= MAX_PRECISION; precision++) { + TYPES[precision] = (precision <= MAX_SHORT_PRECISION) ? new ShortTimeWithTimeZoneType(precision) : new LongTimeWithTimeZoneType(precision); + } + } + + public static final TimeWithTimeZoneType TIME_TZ_SECONDS = createTimeWithTimeZoneType(0); + public static final TimeWithTimeZoneType TIME_TZ_MILLIS = createTimeWithTimeZoneType(3); + public static final TimeWithTimeZoneType TIME_TZ_MICROS = createTimeWithTimeZoneType(6); + public static final TimeWithTimeZoneType TIME_TZ_NANOS = createTimeWithTimeZoneType(9); + public static final TimeWithTimeZoneType TIME_TZ_PICOS = createTimeWithTimeZoneType(12); + /** * @deprecated Use {@link #createTimeWithTimeZoneType} instead. */ @Deprecated - public static final TimeWithTimeZoneType TIME_WITH_TIME_ZONE = new ShortTimeWithTimeZoneType(DEFAULT_PRECISION); + // Use singleton for backwards compatibility with code checking `type == TIME_WITH_TIME_ZONE` + public static final TimeWithTimeZoneType TIME_WITH_TIME_ZONE = TIME_TZ_MILLIS; private final int precision; public static TimeWithTimeZoneType createTimeWithTimeZoneType(int precision) { - if (precision == DEFAULT_PRECISION) { - // Use singleton for backwards compatibility with code checking `type == TIME_WITH_TIME_ZONE` - return TIME_WITH_TIME_ZONE; - } - if (precision < 0 || precision > MAX_PRECISION) { throw new TrinoException(NUMERIC_VALUE_OUT_OF_RANGE, format("TIME WITH TIME ZONE precision must be in range [0, %s]: %s", MAX_PRECISION, precision)); } - - if (precision <= MAX_SHORT_PRECISION) { - return new ShortTimeWithTimeZoneType(precision); - } - - return new LongTimeWithTimeZoneType(precision); + return TYPES[precision]; } protected TimeWithTimeZoneType(int precision, Class javaType) diff --git a/docs/pom.xml b/docs/pom.xml index 05b056481356..a98ae5238afa 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT trino-docs diff --git a/docs/src/main/sphinx/admin/fault-tolerant-execution.rst b/docs/src/main/sphinx/admin/fault-tolerant-execution.rst index 6f8d4ca182f3..93f517bc53d9 100644 --- a/docs/src/main/sphinx/admin/fault-tolerant-execution.rst +++ b/docs/src/main/sphinx/admin/fault-tolerant-execution.rst @@ -46,7 +46,7 @@ depending on the desired :ref:`retry policy `. * Fault-tolerant execution of :ref:`read operations ` is supported by all connectors. - * Fault tolerant execution of :ref:`write operations ` + * Fault-tolerant execution of :ref:`write operations ` is supported by the following connectors: * :doc:`/connector/bigquery` diff --git a/docs/src/main/sphinx/admin/properties-optimizer.rst b/docs/src/main/sphinx/admin/properties-optimizer.rst index 43c6292780dc..153cfa77a8ea 100644 --- a/docs/src/main/sphinx/admin/properties-optimizer.rst +++ b/docs/src/main/sphinx/admin/properties-optimizer.rst @@ -210,3 +210,33 @@ The minimum number of join build side rows required to use partitioned join look If the build side of a join is estimated to be smaller than the configured threshold, single threaded join lookup is used to improve join performance. A value of ``0`` disables this optimization. + +``optimizer.min-input-size-per-task`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* **Type:** :ref:`prop-type-data-size` +* **Default value:** ``5GB`` +* **Min allowed value:** ``0MB`` +* **Session property:** ``min_input_size_per_task`` + +The minimum input size required per task. This will help optimizer to determine hash +partition count for joins and aggregations. Limiting hash partition count for small queries +increases concurrency on large clusters where multiple small queries are running concurrently. +The estimated value will always be between ``min_hash_partition_count`` and +``max_hash_partition_count`` session property. +A value of ``0MB`` disables this optimization. + +``optimizer.min-input-rows-per-task`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* **Type:** :ref:`prop-type-integer` +* **Default value:** ``10000000`` +* **Min allowed value:** ``0`` +* **Session property:** ``min_input_rows_per_task`` + +The minimum number of input rows required per task. This will help optimizer to determine hash +partition count for joins and aggregations. Limiting hash partition count for small queries +increases concurrency on large clusters where multiple small queries are running concurrently. +The estimated value will always be between ``min_hash_partition_count`` and +``max_hash_partition_count`` session property. +A value of ``0`` disables this optimization. diff --git a/docs/src/main/sphinx/admin/properties-query-management.rst b/docs/src/main/sphinx/admin/properties-query-management.rst index 8e324ce5c7cc..9e770ef51885 100644 --- a/docs/src/main/sphinx/admin/properties-query-management.rst +++ b/docs/src/main/sphinx/admin/properties-query-management.rst @@ -29,14 +29,24 @@ stages of a query. You can use the following execution policies: dependencies typically prevent full processing and cause longer queue times which increases the query wall time overall. -``query.hash-partition-count`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``query.max-hash-partition-count`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * **Type:** :ref:`prop-type-integer` * **Default value:** ``100`` -* **Session property:** ``hash_partition_count`` +* **Session property:** ``max_hash_partition_count`` -The number of partitions to use for processing distributed operations, such as +The maximum number of partitions to use for processing distributed operations, such as +joins, aggregations, partitioned window functions and others. + +``query.min-hash-partition-count`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* **Type:** :ref:`prop-type-integer` +* **Default value:** ``4`` +* **Session property:** ``min_hash_partition_count`` + +The minimum number of partitions to use for processing distributed operations, such as joins, aggregations, partitioned window functions and others. ``query.low-memory-killer.policy`` diff --git a/docs/src/main/sphinx/admin/properties-writer-scaling.rst b/docs/src/main/sphinx/admin/properties-writer-scaling.rst index 4cff3d5234a6..aa093b65cb68 100644 --- a/docs/src/main/sphinx/admin/properties-writer-scaling.rst +++ b/docs/src/main/sphinx/admin/properties-writer-scaling.rst @@ -44,8 +44,7 @@ session property. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * **Type:** :ref:`prop-type-integer` -* **Restrictions:** Must be a power of two -* **Default value:** ``8`` +* **Default value:** The number of physical CPUs of the node with a maximum of 32 Maximum number of concurrent writers per task upto which the task can be scaled when ``task.scale-writers.enabled`` is set. Increasing this value may improve the diff --git a/docs/src/main/sphinx/client/cli.rst b/docs/src/main/sphinx/client/cli.rst index 0d05aabb424e..3f333fd78e56 100644 --- a/docs/src/main/sphinx/client/cli.rst +++ b/docs/src/main/sphinx/client/cli.rst @@ -232,14 +232,18 @@ certificate usage: - The password for the keystore. This must match the password you specified when creating the keystore. * - ``--keystore-type`` - - Keystore type. + - Determined by the keystore file format. The default keystore type is JKS. + This advanced option is only necessary if you use a custom Java + Cryptography Architecture (JCA) provider implementation. * - ``--truststore-password`` - The password for the truststore. This must match the password you specified when creating the truststore. * - ``--truststore-path`` - The location of the Java truststore file that will be used to secure TLS. * - ``--truststore-type`` - - Truststore type. + - Determined by the truststore file format. The default keystore type is + JKS. This advanced option is only necessary if you use a custom Java + Cryptography Architecture (JCA) provider implementation. * - ``--use-system-truststore`` - Verify the server certificate using the system truststore of the operating system. Windows and macOS are supported. For other operating diff --git a/docs/src/main/sphinx/connector/accumulo.rst b/docs/src/main/sphinx/connector/accumulo.rst index 89099f7e7aca..7f3d27bf6d71 100644 --- a/docs/src/main/sphinx/connector/accumulo.rst +++ b/docs/src/main/sphinx/connector/accumulo.rst @@ -37,9 +37,9 @@ To connect to Accumulo, you need: Connector configuration ----------------------- -Create ``etc/catalog/accumulo.properties`` -to mount the ``accumulo`` connector as the ``accumulo`` catalog, -replacing the ``accumulo.xxx`` properties as required: +Create ``etc/catalog/example.properties`` to mount the ``accumulo`` connector as +the ``example`` catalog, with the following connector properties as appropriate +for your setup: .. code-block:: text @@ -49,6 +49,8 @@ replacing the ``accumulo.xxx`` properties as required: accumulo.username=username accumulo.password=password +Replace the ``accumulo.xxx`` properties as required. + Configuration variables ----------------------- @@ -79,7 +81,7 @@ clause of your table definition. Simply issue a ``CREATE TABLE`` statement to create a new Trino/Accumulo table:: - CREATE TABLE myschema.scientists ( + CREATE TABLE example_schema.scientists ( recordkey VARCHAR, name VARCHAR, age BIGINT, @@ -88,7 +90,7 @@ Simply issue a ``CREATE TABLE`` statement to create a new Trino/Accumulo table:: .. code-block:: sql - DESCRIBE myschema.scientists; + DESCRIBE example_schema.scientists; .. code-block:: text @@ -122,7 +124,7 @@ For example: .. code-block:: sql - CREATE TABLE myschema.scientists ( + CREATE TABLE example_schema.scientists ( recordkey VARCHAR, name VARCHAR, age BIGINT, @@ -134,7 +136,7 @@ For example: .. code-block:: sql - DESCRIBE myschema.scientists; + DESCRIBE example_schema.scientists; .. code-block:: text @@ -156,13 +158,13 @@ You can then issue ``INSERT`` statements to put data into Accumulo. .. code-block:: sql - INSERT INTO myschema.scientists VALUES + INSERT INTO example_schema.scientists VALUES ('row1', 'Grace Hopper', 109, DATE '1906-12-09' ), ('row2', 'Alan Turing', 103, DATE '1912-06-23' ); .. code-block:: sql - SELECT * FROM myschema.scientists; + SELECT * FROM example_schema.scientists; .. code-block:: text @@ -180,14 +182,14 @@ little younger.) .. code-block:: bash $ accumulo shell -u root -p secret - root@default> table myschema.scientists - root@default myschema.scientists> insert row3 metadata name "Tim Berners-Lee" - root@default myschema.scientists> insert row3 metadata age 60 - root@default myschema.scientists> insert row3 metadata date 5321 + root@default> table example_schema.scientists + root@default example_schema.scientists> insert row3 metadata name "Tim Berners-Lee" + root@default example_schema.scientists> insert row3 metadata age 60 + root@default example_schema.scientists> insert row3 metadata date 5321 .. code-block:: sql - SELECT * FROM myschema.scientists; + SELECT * FROM example_schema.scientists; .. code-block:: text @@ -205,7 +207,7 @@ tables. .. code-block:: sql - DROP TABLE myschema.scientists; + DROP TABLE example_schema.scientists; Indexing columns ---------------- @@ -236,7 +238,7 @@ should be using the default ``lexicoder`` serializer). .. code-block:: sql - CREATE TABLE myschema.scientists ( + CREATE TABLE example_schema.scientists ( recordkey VARCHAR, name VARCHAR, age BIGINT, @@ -255,9 +257,9 @@ tables to store the index and metrics. root@default> tables accumulo.metadata accumulo.root - myschema.scientists - myschema.scientists_idx - myschema.scientists_idx_metrics + example_schema.scientists + example_schema.scientists_idx + example_schema.scientists_idx_metrics trace After inserting data, we can look at the index table and see there are @@ -266,13 +268,13 @@ queries this index table .. code-block:: sql - INSERT INTO myschema.scientists VALUES + INSERT INTO example_schema.scientists VALUES ('row1', 'Grace Hopper', 109, DATE '1906-12-09'), ('row2', 'Alan Turing', 103, DATE '1912-06-23'); .. code-block:: text - root@default> scan -t myschema.scientists_idx + root@default> scan -t example_schema.scientists_idx -21011 metadata_date:row2 [] -23034 metadata_date:row1 [] 103 metadata_age:row2 [] @@ -290,7 +292,7 @@ scans the data table. .. code-block:: sql - SELECT * FROM myschema.scientists WHERE age = 109; + SELECT * FROM example_schema.scientists WHERE age = 109; .. code-block:: text @@ -428,7 +430,7 @@ Table property usage example: .. code-block:: sql - CREATE TABLE myschema.scientists ( + CREATE TABLE example_schema.scientists ( recordkey VARCHAR, name VARCHAR, age BIGINT, @@ -463,7 +465,7 @@ Session properties You can change the default value of a session property by using :doc:`/sql/set-session`. Note that session properties are prefixed with the catalog name:: - SET SESSION accumulo.column_filter_optimizations_enabled = false; + SET SESSION example.column_filter_optimizations_enabled = false; ============================================= ============= ======================================================================================================= Property name Default value Description @@ -520,7 +522,7 @@ specifying the fully-qualified Java class name in the connector configuration. .. code-block:: sql - CREATE TABLE myschema.scientists ( + CREATE TABLE example_schema.scientists ( recordkey VARCHAR, name VARCHAR, age BIGINT, @@ -533,13 +535,13 @@ specifying the fully-qualified Java class name in the connector configuration. .. code-block:: sql - INSERT INTO myschema.scientists VALUES + INSERT INTO example_schema.scientists VALUES ('row1', 'Grace Hopper', 109, DATE '1906-12-09' ), ('row2', 'Alan Turing', 103, DATE '1912-06-23' ); .. code-block:: text - root@default> scan -t myschema.scientists + root@default> scan -t example_schema.scientists row1 metadata:age [] \x08\x80\x00\x00\x00\x00\x00\x00m row1 metadata:date [] \x08\x7F\xFF\xFF\xFF\xFF\xFF\xA6\x06 row1 metadata:name [] Grace Hopper @@ -549,7 +551,7 @@ specifying the fully-qualified Java class name in the connector configuration. .. code-block:: sql - CREATE TABLE myschema.stringy_scientists ( + CREATE TABLE example_schema.stringy_scientists ( recordkey VARCHAR, name VARCHAR, age BIGINT, @@ -562,13 +564,13 @@ specifying the fully-qualified Java class name in the connector configuration. .. code-block:: sql - INSERT INTO myschema.stringy_scientists VALUES + INSERT INTO example_schema.stringy_scientists VALUES ('row1', 'Grace Hopper', 109, DATE '1906-12-09' ), ('row2', 'Alan Turing', 103, DATE '1912-06-23' ); .. code-block:: text - root@default> scan -t myschema.stringy_scientists + root@default> scan -t example_schema.stringy_scientists row1 metadata:age [] 109 row1 metadata:date [] -23034 row1 metadata:name [] Grace Hopper @@ -578,7 +580,7 @@ specifying the fully-qualified Java class name in the connector configuration. .. code-block:: sql - CREATE TABLE myschema.custom_scientists ( + CREATE TABLE example_schema.custom_scientists ( recordkey VARCHAR, name VARCHAR, age BIGINT, diff --git a/docs/src/main/sphinx/connector/alter-table-limitation.fragment b/docs/src/main/sphinx/connector/alter-table-limitation.fragment index fdff95e63324..f6de252f1d5c 100644 --- a/docs/src/main/sphinx/connector/alter-table-limitation.fragment +++ b/docs/src/main/sphinx/connector/alter-table-limitation.fragment @@ -6,11 +6,11 @@ example, the following statement is supported: .. code-block:: sql - ALTER TABLE catalog.schema_one.table_one RENAME TO catalog.schema_one.table_two + ALTER TABLE example.schema_one.table_one RENAME TO example.schema_one.table_two The following statement attempts to rename a table across schemas, and therefore is not supported: .. code-block:: sql - ALTER TABLE catalog.schema_one.table_one RENAME TO catalog.schema_two.table_two + ALTER TABLE example.schema_one.table_one RENAME TO example.schema_two.table_two diff --git a/docs/src/main/sphinx/connector/bigquery.rst b/docs/src/main/sphinx/connector/bigquery.rst index bd71b0454bc7..4b37834d4a7f 100644 --- a/docs/src/main/sphinx/connector/bigquery.rst +++ b/docs/src/main/sphinx/connector/bigquery.rst @@ -65,10 +65,9 @@ Configuration ------------- To configure the BigQuery connector, create a catalog properties file in -``etc/catalog`` named, for example, ``bigquery.properties``, to mount the -BigQuery connector as the ``bigquery`` catalog. Create the file with the -following contents, replacing the connection properties as appropriate for -your setup: +``etc/catalog`` named ``example.properties``, to mount the BigQuery connector as +the ``example`` catalog. Create the file with the following contents, replacing +the connection properties as appropriate for your setup: .. code-block:: text @@ -272,9 +271,10 @@ No other types are supported. System tables ------------- -For each Trino table which maps to BigQuery view there exists a system table which exposes BigQuery view definition. -Given a BigQuery view ``customer_view`` you can send query -``SELECT * customer_view$view_definition`` to see the SQL which defines view in BigQuery. +For each Trino table which maps to BigQuery view there exists a system table +which exposes BigQuery view definition. Given a BigQuery view ``example_view`` +you can send query ``SELECT * example_view$view_definition`` to see the SQL +which defines view in BigQuery. .. _bigquery_special_columns: @@ -293,12 +293,12 @@ can be selected directly, or used in conditional statements. For example, you can inspect the partition date and time for each record:: SELECT *, "$partition_date", "$partition_time" - FROM bigquery.web.page_views; + FROM example.web.page_views; Retrieve all records stored in the partition ``_PARTITIONDATE = '2022-04-07'``:: SELECT * - FROM bigquery.web.page_views + FROM example.web.page_views WHERE "$partition_date" = date '2022-04-07'; .. note:: @@ -350,7 +350,7 @@ For example, group and concatenate all employee IDs by manager ID:: * FROM TABLE( - bigquery.system.query( + example.system.query( query => 'SELECT manager_id, STRING_AGG(employee_id) FROM diff --git a/docs/src/main/sphinx/connector/blackhole.rst b/docs/src/main/sphinx/connector/blackhole.rst index e9ae0c8f8923..b1da97b1670e 100644 --- a/docs/src/main/sphinx/connector/blackhole.rst +++ b/docs/src/main/sphinx/connector/blackhole.rst @@ -24,8 +24,8 @@ You shouldn't rely on the content of such rows. Configuration ------------- -To configure the Black Hole connector, create a catalog properties file -``etc/catalog/blackhole.properties`` with the following contents: +Create ``etc/catalog/example.properties`` to mount the ``blackhole`` connector +as the ``example`` catalog, with the following contents: .. code-block:: text @@ -36,23 +36,23 @@ Examples Create a table using the blackhole connector:: - CREATE TABLE blackhole.test.nation AS + CREATE TABLE example.test.nation AS SELECT * from tpch.tiny.nation; Insert data into a table in the blackhole connector:: - INSERT INTO blackhole.test.nation + INSERT INTO example.test.nation SELECT * FROM tpch.tiny.nation; Select from the blackhole connector:: - SELECT count(*) FROM blackhole.test.nation; + SELECT count(*) FROM example.test.nation; The above query always returns zero. Create a table with a constant number of rows (500 * 1000 * 2000):: - CREATE TABLE blackhole.test.nation ( + CREATE TABLE example.test.nation ( nationkey bigint, name varchar ) @@ -64,14 +64,14 @@ Create a table with a constant number of rows (500 * 1000 * 2000):: Now query it:: - SELECT count(*) FROM blackhole.test.nation; + SELECT count(*) FROM example.test.nation; The above query returns 1,000,000,000. Length of variable length columns can be controlled using the ``field_length`` table property (default value is equal to 16):: - CREATE TABLE blackhole.test.nation ( + CREATE TABLE example.test.nation ( nationkey bigint, name varchar ) @@ -87,7 +87,7 @@ using the ``page_processing_delay`` table property. Setting this property to ``5s`` leads to a 5 second delay before consuming or producing a new page:: - CREATE TABLE blackhole.test.delay ( + CREATE TABLE example.test.delay ( dummy bigint ) WITH ( diff --git a/docs/src/main/sphinx/connector/cassandra.rst b/docs/src/main/sphinx/connector/cassandra.rst index c4b3682b2728..b7c52f86da37 100644 --- a/docs/src/main/sphinx/connector/cassandra.rst +++ b/docs/src/main/sphinx/connector/cassandra.rst @@ -22,9 +22,9 @@ Configuration ------------- To configure the Cassandra connector, create a catalog properties file -``etc/catalog/cassandra.properties`` with the following contents, -replacing ``host1,host2`` with a comma-separated list of the Cassandra -nodes, used to discovery the cluster topology: +``etc/catalog/example.properties`` with the following contents, replacing +``host1,host2`` with a comma-separated list of the Cassandra nodes, used to +discovery the cluster topology: .. code-block:: text @@ -165,17 +165,17 @@ Querying Cassandra tables ------------------------- The ``users`` table is an example Cassandra table from the Cassandra -`Getting Started`_ guide. It can be created along with the ``mykeyspace`` +`Getting Started`_ guide. It can be created along with the ``example_keyspace`` keyspace using Cassandra's cqlsh (CQL interactive terminal): .. _Getting Started: https://cassandra.apache.org/doc/latest/cassandra/getting_started/index.html .. code-block:: text - cqlsh> CREATE KEYSPACE mykeyspace + cqlsh> CREATE KEYSPACE example_keyspace ... WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }; - cqlsh> USE mykeyspace; - cqlsh:mykeyspace> CREATE TABLE users ( + cqlsh> USE example_keyspace; + cqlsh:example_keyspace> CREATE TABLE users ( ... user_id int PRIMARY KEY, ... fname text, ... lname text @@ -183,7 +183,7 @@ keyspace using Cassandra's cqlsh (CQL interactive terminal): This table can be described in Trino:: - DESCRIBE cassandra.mykeyspace.users; + DESCRIBE example.example_keyspace.users; .. code-block:: text @@ -196,7 +196,7 @@ This table can be described in Trino:: This table can then be queried in Trino:: - SELECT * FROM cassandra.mykeyspace.users; + SELECT * FROM example.example_keyspace.users; .. _cassandra-type-mapping: diff --git a/docs/src/main/sphinx/connector/clickhouse.rst b/docs/src/main/sphinx/connector/clickhouse.rst index 8dec13c5dc44..ee7d91bc61e6 100644 --- a/docs/src/main/sphinx/connector/clickhouse.rst +++ b/docs/src/main/sphinx/connector/clickhouse.rst @@ -27,9 +27,8 @@ The connector can query a ClickHouse server. Create a catalog properties file that specifies the ClickHouse connector by setting the ``connector.name`` to ``clickhouse``. -For example, to access a server as ``clickhouse``, create the file -``etc/catalog/clickhouse.properties``. Replace the connection properties as -appropriate for your setup: +For example, create the file ``etc/catalog/example.properties``. Replace the +connection properties as appropriate for your setup: .. code-block:: none @@ -109,27 +108,27 @@ Querying ClickHouse The ClickHouse connector provides a schema for every ClickHouse *database*. Run ``SHOW SCHEMAS`` to see the available ClickHouse databases:: - SHOW SCHEMAS FROM myclickhouse; + SHOW SCHEMAS FROM example; If you have a ClickHouse database named ``web``, run ``SHOW TABLES`` to view the tables in this database:: - SHOW TABLES FROM myclickhouse.web; + SHOW TABLES FROM example.web; Run ``DESCRIBE`` or ``SHOW COLUMNS`` to list the columns in the ``clicks`` table in the ``web`` databases:: - DESCRIBE myclickhouse.web.clicks; - SHOW COLUMNS FROM clickhouse.web.clicks; + DESCRIBE example.web.clicks; + SHOW COLUMNS FROM example.web.clicks; Run ``SELECT`` to access the ``clicks`` table in the ``web`` database:: - SELECT * FROM myclickhouse.web.clicks; + SELECT * FROM example.web.clicks; .. note:: If you used a different name for your catalog properties file, use - that catalog name instead of ``myclickhouse`` in the above examples. + that catalog name instead of ``example`` in the above examples. Table properties ---------------- diff --git a/docs/src/main/sphinx/connector/delta-lake.rst b/docs/src/main/sphinx/connector/delta-lake.rst index 76aec22883eb..f9671948812b 100644 --- a/docs/src/main/sphinx/connector/delta-lake.rst +++ b/docs/src/main/sphinx/connector/delta-lake.rst @@ -35,8 +35,8 @@ The connector recognizes Delta tables created in the metastore by the Databricks runtime. If non-Delta tables are present in the metastore, as well, they are not visible to the connector. -To configure the Delta Lake connector, create a catalog properties file, for -example ``etc/catalog/delta.properties``, that references the ``delta-lake`` +To configure the Delta Lake connector, create a catalog properties file +``etc/catalog/example.properties`` that references the ``delta-lake`` connector. Update the ``hive.metastore.uri`` with the URI of your Hive metastore Thrift service: @@ -217,6 +217,9 @@ connector. - A decimal value in the range (0, 1] used as a minimum for weights assigned to each split. A low value may improve performance on tables with small files. A higher value may improve performance for queries with highly skewed aggregations or joins. - 0.05 + * - ``parquet.max-read-block-row-count`` + - Sets the maximum number of rows read in a batch. + - ``8192`` * - ``parquet.optimized-writer.enabled`` - Whether the optimized writer should be used when writing Parquet files. The equivalent catalog session property is @@ -501,14 +504,14 @@ You can create a schema with the :doc:`/sql/create-schema` statement and the subdirectory under the schema location. Data files for tables in this schema using the default location are cleaned up if the table is dropped:: - CREATE SCHEMA delta.my_schema + CREATE SCHEMA example.example_schema WITH (location = 's3://my-bucket/a/path'); Optionally, the location can be omitted. Tables in this schema must have a location included when you create them. The data files for these tables are not removed if the table is dropped:: - CREATE SCHEMA delta.my_schema; + CREATE SCHEMA example.example_schema; .. _delta-lake-create-table: @@ -518,7 +521,7 @@ Creating tables When Delta tables exist in storage, but not in the metastore, Trino can be used to register them:: - CREATE TABLE delta.default.my_table ( + CREATE TABLE example.default.example_table ( dummy bigint ) WITH ( @@ -541,7 +544,7 @@ If the specified location does not already contain a Delta table, the connector automatically writes the initial transaction log entries and registers the table in the metastore. As a result, any Databricks engine can write to the table:: - CREATE TABLE delta.default.new_table (id bigint, address varchar); + CREATE TABLE example.default.new_table (id bigint, address varchar); The Delta Lake connector also supports creating tables using the :doc:`CREATE TABLE AS ` syntax. @@ -563,7 +566,7 @@ There are three table properties available for use in table creation. The following example uses all three table properties:: - CREATE TABLE delta.default.my_partitioned_table + CREATE TABLE example.default.example_partitioned_table WITH ( location = 's3://my-bucket/a/path', partitioned_by = ARRAY['regionkey'], @@ -581,7 +584,7 @@ The connector can register table into the metastore with existing transaction lo The ``system.register_table`` procedure allows the caller to register an existing delta lake table in the metastore, using its existing transaction logs and data files:: - CALL delta.system.register_table(schema_name => 'testdb', table_name => 'customer_orders', table_location => 's3://my-bucket/a/path') + CALL example.system.register_table(schema_name => 'testdb', table_name => 'customer_orders', table_location => 's3://my-bucket/a/path') To prevent unauthorized users from accessing data, this procedure is disabled by default. The procedure is enabled only when ``delta.register-table-procedure.enabled`` is set to ``true``. @@ -658,7 +661,7 @@ limit the amount of data used to generate the table statistics: .. code-block:: SQL - ANALYZE my_table WITH(files_modified_after = TIMESTAMP '2021-08-23 + ANALYZE example_table WITH(files_modified_after = TIMESTAMP '2021-08-23 16:43:01.321 Z') As a result, only files newer than the specified time stamp are used in the @@ -669,7 +672,7 @@ property: .. code-block:: SQL - ANALYZE my_table WITH(columns = ARRAY['nationkey', 'regionkey']) + ANALYZE example_table WITH(columns = ARRAY['nationkey', 'regionkey']) To run ``ANALYZE`` with ``columns`` more than once, the next ``ANALYZE`` must run on the same set or a subset of the original columns used. @@ -693,7 +696,7 @@ extended statistics for a specified table in a specified schema: .. code-block:: - CALL delta_catalog.system.drop_extended_stats('my_schema', 'my_table') + CALL example.system.drop_extended_stats('example_schema', 'example_table') Memory usage @@ -721,7 +724,7 @@ as follows: .. code-block:: shell - CALL mydeltacatalog.system.vacuum('myschemaname', 'mytablename', '7d'); + CALL example.system.vacuum('exampleschemaname', 'exampletablename', '7d'); All parameters are required, and must be presented in the following order: diff --git a/docs/src/main/sphinx/connector/druid.rst b/docs/src/main/sphinx/connector/druid.rst index 698e58491d67..11d004564a4e 100644 --- a/docs/src/main/sphinx/connector/druid.rst +++ b/docs/src/main/sphinx/connector/druid.rst @@ -25,8 +25,8 @@ Create a catalog properties file that specifies the Druid connector by setting the ``connector.name`` to ``druid`` and configuring the ``connection-url`` with the JDBC string to connect to Druid. -For example, to access a database as ``druid``, create the file -``etc/catalog/druid.properties``. Replace ``BROKER:8082`` with the correct +For example, to access a database as ``example``, create the file +``etc/catalog/example.properties``. Replace ``BROKER:8082`` with the correct host and port of your Druid broker. .. code-block:: properties @@ -43,7 +43,7 @@ secured by basic authentication by updating the URL and adding credentials: connection-user=root connection-password=secret -Now you can access your Druid database in Trino with the ``druiddb`` catalog +Now you can access your Druid database in Trino with the ``example`` catalog name from the properties file. The ``connection-user`` and ``connection-password`` are typically required and @@ -144,7 +144,7 @@ to split and then count the number of comma-separated values in a column:: num_reports FROM TABLE( - druid.system.query( + example.system.query( query => 'SELECT MV_LENGTH( STRING_TO_MV(direct_reports, ",") diff --git a/docs/src/main/sphinx/connector/elasticsearch.rst b/docs/src/main/sphinx/connector/elasticsearch.rst index 7b244fce960e..d7acba6e10f5 100644 --- a/docs/src/main/sphinx/connector/elasticsearch.rst +++ b/docs/src/main/sphinx/connector/elasticsearch.rst @@ -17,8 +17,8 @@ Configuration ------------- To configure the Elasticsearch connector, create a catalog properties file -``etc/catalog/elasticsearch.properties`` with the following contents, -replacing the properties as appropriate: +``etc/catalog/example.properties`` with the following contents, replacing the +properties as appropriate for your setup: .. code-block:: text @@ -462,7 +462,7 @@ documents in the ``orders`` index where the country name is ``ALGERIA``:: * FROM TABLE( - elasticsearch.system.raw_query( + example.system.raw_query( schema => 'sales', index => 'orders', query => '{ diff --git a/docs/src/main/sphinx/connector/hive-s3.rst b/docs/src/main/sphinx/connector/hive-s3.rst index a17adfd63a93..d0826e79fe01 100644 --- a/docs/src/main/sphinx/connector/hive-s3.rst +++ b/docs/src/main/sphinx/connector/hive-s3.rst @@ -10,6 +10,8 @@ uses an S3 prefix, rather than an HDFS prefix. Trino uses its own S3 filesystem for the URI prefixes ``s3://``, ``s3n://`` and ``s3a://``. +.. _hive-s3-configuration: + S3 configuration properties --------------------------- @@ -284,6 +286,8 @@ Property name Description value that is not used in any of your IAM ARNs. ======================================================= ================================================================= +.. _hive-s3-tuning-configuration: + Tuning properties ----------------- @@ -315,10 +319,11 @@ Property name Description ``hive.s3.multipart.min-part-size`` Minimum multi-part upload part size. ``5 MB`` ===================================== =========================================================== =============== +.. _hive-s3-data-encryption: + S3 data encryption ------------------ - Trino supports reading and writing encrypted data in S3 using both server-side encryption with S3 managed keys and client-side encryption using either the Amazon KMS or a software plugin to manage AES encryption keys. diff --git a/docs/src/main/sphinx/connector/hive.rst b/docs/src/main/sphinx/connector/hive.rst index e7c0d2b9f61f..1e05170e09e2 100644 --- a/docs/src/main/sphinx/connector/hive.rst +++ b/docs/src/main/sphinx/connector/hive.rst @@ -525,6 +525,9 @@ with Parquet files performed by the Hive connector. definition. The equivalent catalog session property is ``parquet_use_column_names``. - ``true`` + * - ``parquet.max-read-block-row-count`` + - Sets the maximum number of rows read in a batch. + - ``8192`` * - ``parquet.optimized-reader.enabled`` - Whether batched column readers should be used when reading Parquet files for improved performance. Set this property to ``false`` to disable the diff --git a/docs/src/main/sphinx/connector/hudi.rst b/docs/src/main/sphinx/connector/hudi.rst index 213f6f8b6fb4..4acfc00c6560 100644 --- a/docs/src/main/sphinx/connector/hudi.rst +++ b/docs/src/main/sphinx/connector/hudi.rst @@ -26,10 +26,10 @@ metastore configuration properties as the :doc:`Hive connector The connector recognizes Hudi tables synced to the metastore by the `Hudi sync tool `_. -To create a catalog that uses the Hudi connector, create a catalog properties file, -for example ``etc/catalog/example.properties``, that references the ``hudi`` -connector. Update the ``hive.metastore.uri`` with the URI of your Hive metastore -Thrift service: +To create a catalog that uses the Hudi connector, create a catalog properties +file ``etc/catalog/example.properties`` that references the ``hudi`` connector. +Update the ``hive.metastore.uri`` with the URI of your Hive metastore Thrift +service: .. code-block:: properties @@ -119,7 +119,7 @@ Here are some sample queries: .. code-block:: sql - USE a-catalog.myschema; + USE example.example_schema; SELECT symbol, max(ts) FROM stock_ticks_cow diff --git a/docs/src/main/sphinx/connector/iceberg.rst b/docs/src/main/sphinx/connector/iceberg.rst index ef2deb8ae1be..3499f70057cc 100644 --- a/docs/src/main/sphinx/connector/iceberg.rst +++ b/docs/src/main/sphinx/connector/iceberg.rst @@ -253,6 +253,9 @@ with Parquet files performed by the Iceberg connector. * - Property Name - Description - Default + * - ``parquet.max-read-block-row-count`` + - Sets the maximum number of rows read in a batch. + - ``8192`` * - ``parquet.optimized-reader.enabled`` - Whether batched column readers should be used when reading Parquet files for improved performance. Set this property to ``false`` to disable the @@ -349,22 +352,22 @@ subdirectory under the directory corresponding to the schema location. Create a schema on S3:: - CREATE SCHEMA iceberg.my_s3_schema + CREATE SCHEMA example.example_s3_schema WITH (location = 's3://my-bucket/a/path/'); Create a schema on a S3 compatible object storage such as MinIO:: - CREATE SCHEMA iceberg.my_s3a_schema + CREATE SCHEMA example.example_s3a_schema WITH (location = 's3a://my-bucket/a/path/'); Create a schema on HDFS:: - CREATE SCHEMA iceberg.my_hdfs_schema + CREATE SCHEMA example.example_hdfs_schema WITH (location='hdfs://hadoop-master:9000/user/hive/warehouse/a/path/'); Optionally, on HDFS, the location can be omitted:: - CREATE SCHEMA iceberg.my_hdfs_schema; + CREATE SCHEMA example.example_hdfs_schema; .. _iceberg-create-table: @@ -375,7 +378,7 @@ The Iceberg connector supports creating tables using the :doc:`CREATE TABLE ` syntax. Optionally specify the :ref:`table properties ` supported by this connector:: - CREATE TABLE my_table ( + CREATE TABLE example_table ( c1 integer, c2 date, c3 double @@ -424,7 +427,7 @@ The Iceberg connector supports setting ``NOT NULL`` constraints on the table col The ``NOT NULL`` constraint can be set on the columns, while creating tables by using the :doc:`CREATE TABLE ` syntax:: - CREATE TABLE my_table ( + CREATE TABLE example_table ( year INTEGER NOT NULL, name VARCHAR NOT NULL, age INTEGER, @@ -603,7 +606,7 @@ partitioning columns, that can match entire partitions. Given the table definiti from :ref:`Partitioned Tables ` section, the following SQL statement deletes all partitions for which ``country`` is ``US``:: - DELETE FROM iceberg.testdb.customer_orders + DELETE FROM example.testdb.customer_orders WHERE country = 'US' A partition delete is performed if the ``WHERE`` clause meets these conditions. @@ -765,7 +768,7 @@ Transform Description In this example, the table is partitioned by the month of ``order_date``, a hash of ``account_number`` (with 10 buckets), and ``country``:: - CREATE TABLE iceberg.testdb.customer_orders ( + CREATE TABLE example.testdb.customer_orders ( order_id BIGINT, order_date DATE, account_number BIGINT, @@ -785,9 +788,11 @@ For example, you could find the snapshot IDs for the ``customer_orders`` table by running the following query:: SELECT snapshot_id - FROM iceberg.testdb."customer_orders$snapshots" + FROM example.testdb."customer_orders$snapshots" ORDER BY committed_at DESC +.. _iceberg-time-travel: + Time travel queries ^^^^^^^^^^^^^^^^^^^ @@ -800,7 +805,7 @@ snapshot identifier corresponding to the version of the table that needs to be retrieved:: SELECT * - FROM iceberg.testdb.customer_orders FOR VERSION AS OF 8954597067493422955 + FROM example.testdb.customer_orders FOR VERSION AS OF 8954597067493422955 A different approach of retrieving historical data is to specify a point in time in the past, such as a day or week ago. The latest snapshot @@ -808,7 +813,7 @@ of the table taken before or at the specified timestamp in the query is internally used for providing the previous state of the table:: SELECT * - FROM iceberg.testdb.customer_orders FOR TIMESTAMP AS OF TIMESTAMP '2022-03-23 09:59:29.803 Europe/Vienna' + FROM example.testdb.customer_orders FOR TIMESTAMP AS OF TIMESTAMP '2022-03-23 09:59:29.803 Europe/Vienna' Rolling back to a previous snapshot ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -816,13 +821,13 @@ Rolling back to a previous snapshot Use the ``$snapshots`` metadata table to determine the latest snapshot ID of the table like in the following query:: SELECT snapshot_id - FROM iceberg.testdb."customer_orders$snapshots" + FROM example.testdb."customer_orders$snapshots" ORDER BY committed_at DESC LIMIT 1 The procedure ``system.rollback_to_snapshot`` allows the caller to roll back the state of the table to a previous snapshot id:: - CALL iceberg.system.rollback_to_snapshot('testdb', 'customer_orders', 8954597067493422955) + CALL example.system.rollback_to_snapshot('testdb', 'customer_orders', 8954597067493422955) Schema evolution ---------------- @@ -842,18 +847,29 @@ The procedure ``system.register_table`` allows the caller to register an existing Iceberg table in the metastore, using its existing metadata and data files:: - CALL iceberg.system.register_table(schema_name => 'testdb', table_name => 'customer_orders', table_location => 'hdfs://hadoop-master:9000/user/hive/warehouse/customer_orders-581fad8517934af6be1857a903559d44') + CALL example.system.register_table(schema_name => 'testdb', table_name => 'customer_orders', table_location => 'hdfs://hadoop-master:9000/user/hive/warehouse/customer_orders-581fad8517934af6be1857a903559d44') In addition, you can provide a file name to register a table with specific metadata. This may be used to register the table with some specific table state, or may be necessary if the connector cannot automatically figure out the metadata version to use:: - CALL iceberg.system.register_table(schema_name => 'testdb', table_name => 'customer_orders', table_location => 'hdfs://hadoop-master:9000/user/hive/warehouse/customer_orders-581fad8517934af6be1857a903559d44', metadata_file_name => '00003-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json') + CALL example.system.register_table(schema_name => 'testdb', table_name => 'customer_orders', table_location => 'hdfs://hadoop-master:9000/user/hive/warehouse/customer_orders-581fad8517934af6be1857a903559d44', metadata_file_name => '00003-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json') To prevent unauthorized users from accessing data, this procedure is disabled by default. The procedure is enabled only when ``iceberg.register-table-procedure.enabled`` is set to ``true``. +.. _iceberg-unregister-table: + +Unregister table +---------------- +The connector can unregister existing Iceberg tables from the catalog. + +The procedure ``system.unregister_table`` allows the caller to unregister an +existing Iceberg table from the metastores without deleting the data:: + + CALL example.system.unregister_table(schema_name => 'testdb', table_name => 'customer_orders') + Migrating existing tables ------------------------- @@ -896,7 +912,7 @@ Property name Description ================================================== ================================================================ The table definition below specifies format Parquet, partitioning by columns ``c1`` and ``c2``, -and a file system location of ``/var/my_tables/test_table``:: +and a file system location of ``/var/example_tables/test_table``:: CREATE TABLE test_table ( c1 integer, @@ -905,10 +921,10 @@ and a file system location of ``/var/my_tables/test_table``:: WITH ( format = 'PARQUET', partitioning = ARRAY['c1', 'c2'], - location = '/var/my_tables/test_table') + location = '/var/example_tables/test_table') The table definition below specifies format ORC, bloom filter index by columns ``c1`` and ``c2``, -fpp is 0.05, and a file system location of ``/var/my_tables/test_table``:: +fpp is 0.05, and a file system location of ``/var/example_tables/test_table``:: CREATE TABLE test_table ( c1 integer, @@ -916,7 +932,7 @@ fpp is 0.05, and a file system location of ``/var/my_tables/test_table``:: c3 double) WITH ( format = 'ORC', - location = '/var/my_tables/test_table', + location = '/var/example_tables/test_table', orc_bloom_filter_columns = ARRAY['c1', 'c2'], orc_bloom_filter_fpp = 0.05) @@ -937,18 +953,18 @@ can be selected directly, or used in conditional statements. For example, you can inspect the file path for each record:: SELECT *, "$path", "$file_modified_time" - FROM iceberg.web.page_views; + FROM example.web.page_views; Retrieve all records that belong to a specific file using ``"$path"`` filter:: SELECT * - FROM iceberg.web.page_views + FROM example.web.page_views WHERE "$path" = '/usr/iceberg/table/web.page_views/data/file_01.parquet' Retrieve all records that belong to a specific file using ``"$file_modified_time"`` filter:: SELECT * - FROM iceberg.web.page_views + FROM example.web.page_views WHERE "$file_modified_time" = CAST('2022-07-01 01:02:03.456 UTC' AS timestamp with time zone) .. _iceberg-metadata-tables: diff --git a/docs/src/main/sphinx/connector/jdbc-procedures.fragment b/docs/src/main/sphinx/connector/jdbc-procedures.fragment index a8cf6bd8f183..d1f50b8a1c81 100644 --- a/docs/src/main/sphinx/connector/jdbc-procedures.fragment +++ b/docs/src/main/sphinx/connector/jdbc-procedures.fragment @@ -8,5 +8,5 @@ Procedures .. code-block:: sql - USE example.myschema; + USE example.example_schema; CALL system.flush_metadata_cache(); diff --git a/docs/src/main/sphinx/connector/jmx.rst b/docs/src/main/sphinx/connector/jmx.rst index fe10de75f4c9..780d7810ec29 100644 --- a/docs/src/main/sphinx/connector/jmx.rst +++ b/docs/src/main/sphinx/connector/jmx.rst @@ -16,7 +16,7 @@ Configuration ------------- To configure the JMX connector, create a catalog properties file -``etc/catalog/jmx.properties`` with the following contents: +``etc/catalog/example.properties`` with the following contents: .. code-block:: text @@ -62,14 +62,14 @@ The JMX connector provides two schemas. The first one is ``current`` that contains every MBean from every node in the Trino cluster. You can see all of the available MBeans by running ``SHOW TABLES``:: - SHOW TABLES FROM jmx.current; + SHOW TABLES FROM example.current; MBean names map to non-standard table names, and must be quoted with double quotes when referencing them in a query. For example, the following query shows the JVM version of every node:: SELECT node, vmname, vmversion - FROM jmx.current."java.lang:type=runtime"; + FROM example.current."java.lang:type=runtime"; .. code-block:: text @@ -82,7 +82,7 @@ The following query shows the open and maximum file descriptor counts for each node:: SELECT openfiledescriptorcount, maxfiledescriptorcount - FROM jmx.current."java.lang:type=operatingsystem"; + FROM example.current."java.lang:type=operatingsystem"; .. code-block:: text @@ -96,7 +96,7 @@ This allows matching several MBean objects within a single query. The following returns information from the different Trino memory pools on each node:: SELECT freebytes, node, object_name - FROM jmx.current."trino.memory:*type=memorypool*"; + FROM example.current."trino.memory:*type=memorypool*"; .. code-block:: text @@ -111,7 +111,7 @@ The ``history`` schema contains the list of tables configured in the connector p The tables have the same columns as those in the current schema, but with an additional timestamp column that stores the time at which the snapshot was taken:: - SELECT "timestamp", "uptime" FROM jmx.history."java.lang:type=runtime"; + SELECT "timestamp", "uptime" FROM example.history."java.lang:type=runtime"; .. code-block:: text diff --git a/docs/src/main/sphinx/connector/kafka.rst b/docs/src/main/sphinx/connector/kafka.rst index c368d0c3c771..c4f7e848714e 100644 --- a/docs/src/main/sphinx/connector/kafka.rst +++ b/docs/src/main/sphinx/connector/kafka.rst @@ -39,8 +39,8 @@ Configuration ------------- To configure the Kafka connector, create a catalog properties file -``etc/catalog/kafka.properties`` with the following content, -replacing the properties as appropriate. +``etc/catalog/example.properties`` with the following content, replacing the +properties as appropriate. In some cases, such as when using specialized authentication methods, it is necessary to specify additional Kafka client properties in order to access your Kafka cluster. To do so, @@ -627,9 +627,9 @@ for a Kafka message: .. code-block:: json { - "tableName": "your-table-name", - "schemaName": "your-schema-name", - "topicName": "your-topic-name", + "tableName": "example_table_name", + "schemaName": "example_schema_name", + "topicName": "example_topic_name", "key": { "..." }, "message": { "dataFormat": "raw", @@ -717,9 +717,9 @@ The following is an example CSV field definition in a `table definition file .. code-block:: json { - "tableName": "your-table-name", - "schemaName": "your-schema-name", - "topicName": "your-topic-name", + "tableName": "example_table_name", + "schemaName": "example_schema_name", + "topicName": "example_topic_name", "key": { "..." }, "message": { "dataFormat": "csv", @@ -836,9 +836,9 @@ The following is an example JSON field definition in a `table definition file .. code-block:: json { - "tableName": "your-table-name", - "schemaName": "your-schema-name", - "topicName": "your-topic-name", + "tableName": "example_table_name", + "schemaName": "example_schema_name", + "topicName": "example_topic_name", "key": { "..." }, "message": { "dataFormat": "json", @@ -922,9 +922,9 @@ The following example shows an Avro field definition in a `table definition file .. code-block:: json { - "tableName": "your-table-name", - "schemaName": "your-schema-name", - "topicName": "your-topic-name", + "tableName": "example_table_name", + "schemaName": "example_schema_name", + "topicName": "example_topic_name", "key": { "..." }, "message": { @@ -1046,9 +1046,9 @@ file <#table-definition-files>`__ for a Kafka message: .. code-block:: json { - "tableName": "your-table-name", - "schemaName": "your-schema-name", - "topicName": "your-topic-name", + "tableName": "example_table_name", + "schemaName": "example_schema_name", + "topicName": "example_topic_name", "key": { "..." }, "message": { diff --git a/docs/src/main/sphinx/connector/kinesis.rst b/docs/src/main/sphinx/connector/kinesis.rst index 78959072d99f..ce11ef92779c 100644 --- a/docs/src/main/sphinx/connector/kinesis.rst +++ b/docs/src/main/sphinx/connector/kinesis.rst @@ -23,8 +23,9 @@ stored on Amazon S3 (preferred), or stored in a local directory on each Trino no This connector is a **read-only** connector. It can only fetch data from Kinesis streams, but cannot create streams or push data into existing streams. -To configure the Kinesis connector, create a catalog properties file ``etc/catalog/kinesis.properties`` -with the following contents, replacing the properties as appropriate: +To configure the Kinesis connector, create a catalog properties file +``etc/catalog/example.properties`` with the following contents, replacing the +properties as appropriate: .. code-block:: text diff --git a/docs/src/main/sphinx/connector/kudu.rst b/docs/src/main/sphinx/connector/kudu.rst index d7bf385f6660..436188f9fbcf 100644 --- a/docs/src/main/sphinx/connector/kudu.rst +++ b/docs/src/main/sphinx/connector/kudu.rst @@ -98,11 +98,11 @@ The emulation of schemas is disabled by default. In this case all Kudu tables are part of the ``default`` schema. For example, a Kudu table named ``orders`` can be queried in Trino -with ``SELECT * FROM kudu.default.orders`` or simple with ``SELECT * FROM orders`` +with ``SELECT * FROM example.default.orders`` or simple with ``SELECT * FROM orders`` if catalog and schema are set to ``kudu`` and ``default`` respectively. Table names can contain any characters in Kudu. In this case, use double quotes. -E.g. To query a Kudu table named ``special.table!`` use ``SELECT * FROM kudu.default."special.table!"``. +E.g. To query a Kudu table named ``special.table!`` use ``SELECT * FROM example.default."special.table!"``. Example @@ -110,7 +110,7 @@ Example * Create a users table in the default schema:: - CREATE TABLE kudu.default.users ( + CREATE TABLE example.default.users ( user_id int WITH (primary_key = true), first_name varchar, last_name varchar @@ -125,7 +125,7 @@ Example * Describe the table:: - DESCRIBE kudu.default.users; + DESCRIBE example.default.users; .. code-block:: text @@ -138,19 +138,20 @@ Example * Insert some data:: - INSERT INTO kudu.default.users VALUES (1, 'Donald', 'Duck'), (2, 'Mickey', 'Mouse'); + INSERT INTO example.default.users VALUES (1, 'Donald', 'Duck'), (2, 'Mickey', 'Mouse'); * Select the inserted data:: - SELECT * FROM kudu.default.users; + SELECT * FROM example.default.users; .. _behavior-with-schema-emulation: Behavior with schema emulation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If schema emulation has been enabled in the connector properties, i.e. ``etc/catalog/kudu.properties``, -tables are mapped to schemas depending on some conventions. +If schema emulation has been enabled in the connector properties, i.e. +``etc/catalog/example.properties``, tables are mapped to schemas depending on +some conventions. * With ``kudu.schema-emulation.enabled=true`` and ``kudu.schema-emulation.prefix=``, the mapping works like: @@ -424,7 +425,7 @@ Example: .. code-block:: sql - CREATE TABLE mytable ( + CREATE TABLE example_table ( name varchar WITH (primary_key = true, encoding = 'dictionary', compression = 'snappy'), index bigint WITH (nullable = true, encoding = 'runlength', compression = 'lz4'), comment varchar WITH (nullable = true, encoding = 'plain', compression = 'default'), @@ -441,7 +442,7 @@ You can specify the same column properties as on creating a table. Example:: - ALTER TABLE mytable ADD COLUMN extraInfo varchar WITH (nullable = true, encoding = 'plain') + ALTER TABLE example_table ADD COLUMN extraInfo varchar WITH (nullable = true, encoding = 'plain') See also `Column Properties`_. @@ -452,9 +453,9 @@ See also `Column Properties`_. Procedures ---------- -* ``CALL kudu.system.add_range_partition`` see :ref:`managing-range-partitions` +* ``CALL example.system.add_range_partition`` see :ref:`managing-range-partitions` -* ``CALL kudu.system.drop_range_partition`` see :ref:`managing-range-partitions` +* ``CALL example.system.drop_range_partition`` see :ref:`managing-range-partitions` Partitioning design ^^^^^^^^^^^^^^^^^^^ @@ -481,7 +482,7 @@ primary key. Example:: - CREATE TABLE mytable ( + CREATE TABLE example_table ( col1 varchar WITH (primary_key=true), col2 varchar WITH (primary_key=true), ... @@ -499,7 +500,7 @@ of table properties named ``partition_by_second_hash_columns`` and Example:: - CREATE TABLE mytable ( + CREATE TABLE example_table ( col1 varchar WITH (primary_key=true), col2 varchar WITH (primary_key=true), ... @@ -595,13 +596,13 @@ partition. .. code-block:: sql - CALL kudu.system.add_range_partition(, , ) + CALL example.system.add_range_partition(,
, ) - dropping a range partition .. code-block:: sql - CALL kudu.system.drop_range_partition(,
, ) + CALL example.system.drop_range_partition(,
, ) - ````: schema of the table @@ -638,10 +639,10 @@ partition. Example:: - CALL kudu.system.add_range_partition('myschema', 'events', '{"lower": "2018-01-01", "upper": "2018-06-01"}') + CALL example.system.add_range_partition('example_schema', 'events', '{"lower": "2018-01-01", "upper": "2018-06-01"}') This adds a range partition for a table ``events`` in the schema -``myschema`` with the lower bound ``2018-01-01``, more exactly +``example_schema`` with the lower bound ``2018-01-01``, more exactly ``2018-01-01T00:00:00.000``, and the upper bound ``2018-07-01``. Use the SQL statement ``SHOW CREATE TABLE`` to query the existing diff --git a/docs/src/main/sphinx/connector/localfile.rst b/docs/src/main/sphinx/connector/localfile.rst index 37a7be154f2a..736146b11723 100644 --- a/docs/src/main/sphinx/connector/localfile.rst +++ b/docs/src/main/sphinx/connector/localfile.rst @@ -8,8 +8,9 @@ the local file system of each worker. Configuration ------------- -To configure the local file connector, create a catalog properties file -under ``etc/catalog`` named, for example, ``localfile.properties`` with the following contents: +To configure the local file connector, create a catalog properties file under +``etc/catalog`` named, for example, ``example.properties`` with the following +contents: .. code-block:: text @@ -32,7 +33,7 @@ Local file connector schemas and tables The local file connector provides a single schema named ``logs``. You can see all the available tables by running ``SHOW TABLES``:: - SHOW TABLES FROM localfile.logs; + SHOW TABLES FROM example.logs; ``http_request_log`` ^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/src/main/sphinx/connector/mariadb.rst b/docs/src/main/sphinx/connector/mariadb.rst index d24d19ba2818..409fec4cf604 100644 --- a/docs/src/main/sphinx/connector/mariadb.rst +++ b/docs/src/main/sphinx/connector/mariadb.rst @@ -21,11 +21,10 @@ To connect to MariaDB, you need: Configuration ------------- -To configure the MariaDB connector, create a catalog properties file -in ``etc/catalog`` named, for example, ``mariadb.properties``, to -mount the MariaDB connector as the ``mariadb`` catalog. -Create the file with the following contents, replacing the -connection properties as appropriate for your setup: +To configure the MariaDB connector, create a catalog properties file in +``etc/catalog`` named, for example, ``example.properties``, to mount the MariaDB +connector as the ``example`` catalog. Create the file with the following +contents, replacing the connection properties as appropriate for your setup: .. code-block:: text @@ -56,25 +55,25 @@ Querying MariaDB The MariaDB connector provides a schema for every MariaDB *database*. You can see the available MariaDB databases by running ``SHOW SCHEMAS``:: - SHOW SCHEMAS FROM mariadb; + SHOW SCHEMAS FROM example; If you have a MariaDB database named ``web``, you can view the tables in this database by running ``SHOW TABLES``:: - SHOW TABLES FROM mariadb.web; + SHOW TABLES FROM example.web; You can see a list of the columns in the ``clicks`` table in the ``web`` database using either of the following:: - DESCRIBE mariadb.web.clicks; - SHOW COLUMNS FROM mariadb.web.clicks; + DESCRIBE example.web.clicks; + SHOW COLUMNS FROM example.web.clicks; Finally, you can access the ``clicks`` table in the ``web`` database:: - SELECT * FROM mariadb.web.clicks; + SELECT * FROM example.web.clicks; If you used a different name for your catalog properties file, use -that catalog name instead of ``mariadb`` in the above examples. +that catalog name instead of ``example`` in the above examples. .. mariadb-type-mapping: @@ -298,7 +297,7 @@ As an example, select the age of employees by using ``TIMESTAMPDIFF`` and age FROM TABLE( - mariadb.system.query( + example.system.query( query => 'SELECT TIMESTAMPDIFF( YEAR, diff --git a/docs/src/main/sphinx/connector/memory.rst b/docs/src/main/sphinx/connector/memory.rst index 920c6c00c25d..0fe01e5da590 100644 --- a/docs/src/main/sphinx/connector/memory.rst +++ b/docs/src/main/sphinx/connector/memory.rst @@ -9,7 +9,7 @@ Configuration ------------- To configure the Memory connector, create a catalog properties file -``etc/catalog/memory.properties`` with the following contents: +``etc/catalog/example.properties`` with the following contents: .. code-block:: text @@ -24,21 +24,21 @@ Examples Create a table using the Memory connector:: - CREATE TABLE memory.default.nation AS + CREATE TABLE example.default.nation AS SELECT * from tpch.tiny.nation; Insert data into a table in the Memory connector:: - INSERT INTO memory.default.nation + INSERT INTO example.default.nation SELECT * FROM tpch.tiny.nation; Select from the Memory connector:: - SELECT * FROM memory.default.nation; + SELECT * FROM example.default.nation; Drop table:: - DROP TABLE memory.default.nation; + DROP TABLE example.default.nation; .. _memory-type-mapping: diff --git a/docs/src/main/sphinx/connector/memsql.rst b/docs/src/main/sphinx/connector/memsql.rst index 94b46e06096f..9762f2a03c97 100644 --- a/docs/src/main/sphinx/connector/memsql.rst +++ b/docs/src/main/sphinx/connector/memsql.rst @@ -23,11 +23,11 @@ To connect to SingleStore, you need: Configuration ------------- -To configure the SingleStore connector, create a catalog properties file -in ``etc/catalog`` named, for example, ``singlestore.properties``, to -mount the SingleStore connector as the ``singlestore`` catalog. -Create the file with the following contents, replacing the -connection properties as appropriate for your setup: +To configure the SingleStore connector, create a catalog properties file in +``etc/catalog`` named, for example, ``example.properties``, to mount the +SingleStore connector as the ``example`` catalog. Create the file with the +following contents, replacing the connection properties as appropriate for your +setup: .. code-block:: text @@ -93,25 +93,25 @@ Querying SingleStore The SingleStore connector provides a schema for every SingleStore *database*. You can see the available SingleStore databases by running ``SHOW SCHEMAS``:: - SHOW SCHEMAS FROM singlestore; + SHOW SCHEMAS FROM example; If you have a SingleStore database named ``web``, you can view the tables in this database by running ``SHOW TABLES``:: - SHOW TABLES FROM singlestore.web; + SHOW TABLES FROM example.web; You can see a list of the columns in the ``clicks`` table in the ``web`` database using either of the following:: - DESCRIBE singlestore.web.clicks; - SHOW COLUMNS FROM singlestore.web.clicks; + DESCRIBE example.web.clicks; + SHOW COLUMNS FROM example.web.clicks; Finally, you can access the ``clicks`` table in the ``web`` database:: - SELECT * FROM singlestore.web.clicks; + SELECT * FROM example.web.clicks; If you used a different name for your catalog properties file, use -that catalog name instead of ``singlestore`` in the above examples. +that catalog name instead of ``example`` in the above examples. .. _singlestore-type-mapping: diff --git a/docs/src/main/sphinx/connector/mongodb.rst b/docs/src/main/sphinx/connector/mongodb.rst index c241a43f3cc9..c7772c60b976 100644 --- a/docs/src/main/sphinx/connector/mongodb.rst +++ b/docs/src/main/sphinx/connector/mongodb.rst @@ -24,7 +24,7 @@ Configuration ------------- To configure the MongoDB connector, create a catalog properties file -``etc/catalog/mongodb.properties`` with the following contents, +``etc/catalog/example.properties`` with the following contents, replacing the properties as appropriate: .. code-block:: text @@ -49,11 +49,9 @@ The following configuration properties are available: ========================================== ============================================================== Property name Description ========================================== ============================================================== -``mongodb.seeds`` List of all MongoDB servers ``mongodb.connection-url`` The connection url that the driver uses to connect to a MongoDB deployment ``mongodb.schema-collection`` A collection which contains schema information ``mongodb.case-insensitive-name-matching`` Match database and collection names case insensitively -``mongodb.credentials`` List of credentials ``mongodb.min-connections-per-host`` The minimum size of the connection pool per host ``mongodb.connections-per-host`` The maximum size of the connection pool per host ``mongodb.max-wait-time`` The maximum wait time @@ -71,13 +69,6 @@ Property name Description ``mongodb.cursor-batch-size`` The number of elements to return in a batch ========================================== ============================================================== -``mongodb.seeds`` -^^^^^^^^^^^^^^^^^ - -Comma-separated list of ``hostname[:port]`` all MongoDB servers in the same replica set, or a list of MongoDB servers in the same sharded cluster. If a port is not specified, port 27017 will be used. - -This property is deprecated and will be removed in a future release. Use ``mongodb.connection-url`` property instead. - ``mongodb.connection-url`` ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -92,7 +83,8 @@ used. The user/pass credentials must be for a user with write access to the See the `MongoDB Connection URI `_ for more information. -This property is required; there is no default. A connection url or seeds must be provided to connect to a MongoDB deployment. +This property is required; there is no default. A connection URL must be +provided to connect to a MongoDB deployment. ``mongodb.schema-collection`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -114,13 +106,6 @@ Match database and collection names case insensitively. This property is optional; the default is ``false``. -``mongodb.credentials`` -^^^^^^^^^^^^^^^^^^^^^^^ - -A comma separated list of ``username:password@database`` credentials. - -This property is optional; no default value. The ``database`` should be the authentication database for the user (e.g. ``admin``). - ``mongodb.min-connections-per-host`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -223,7 +208,7 @@ The required replica set name. With this option set, the MongoClient instance pe #. Connect in replica set mode, and discover all members of the set based on the given servers #. Make sure that the set name reported by all members matches the required set name. -#. Refuse to service any requests, if any member of the seed list is not part of a replica set with the required name. +#. Refuse to service any requests, if authenticated user is not part of a replica set with the required name. This property is optional; no default value. @@ -526,7 +511,7 @@ For example, get all rows where ``regionkey`` field is 0:: * FROM TABLE( - mongodb.system.query( + example.system.query( database => 'tpch', collection => 'region', filter => '{ regionkey: 0 }' diff --git a/docs/src/main/sphinx/connector/mysql.rst b/docs/src/main/sphinx/connector/mysql.rst index f1451563e9fe..5588c2038a09 100644 --- a/docs/src/main/sphinx/connector/mysql.rst +++ b/docs/src/main/sphinx/connector/mysql.rst @@ -22,11 +22,10 @@ To connect to MySQL, you need: Configuration ------------- -To configure the MySQL connector, create a catalog properties file -in ``etc/catalog`` named, for example, ``mysql.properties``, to -mount the MySQL connector as the ``mysql`` catalog. -Create the file with the following contents, replacing the -connection properties as appropriate for your setup: +To configure the MySQL connector, create a catalog properties file in +``etc/catalog`` named, for example, ``example.properties``, to mount the MySQL +connector as the ``mysql`` catalog. Create the file with the following contents, +replacing the connection properties as appropriate for your setup: .. code-block:: text @@ -266,25 +265,25 @@ Querying MySQL The MySQL connector provides a schema for every MySQL *database*. You can see the available MySQL databases by running ``SHOW SCHEMAS``:: - SHOW SCHEMAS FROM mysql; + SHOW SCHEMAS FROM example; If you have a MySQL database named ``web``, you can view the tables in this database by running ``SHOW TABLES``:: - SHOW TABLES FROM mysql.web; + SHOW TABLES FROM example.web; You can see a list of the columns in the ``clicks`` table in the ``web`` database using either of the following:: - DESCRIBE mysql.web.clicks; - SHOW COLUMNS FROM mysql.web.clicks; + DESCRIBE example.web.clicks; + SHOW COLUMNS FROM example.web.clicks; Finally, you can access the ``clicks`` table in the ``web`` database:: - SELECT * FROM mysql.web.clicks; + SELECT * FROM example.web.clicks; If you used a different name for your catalog properties file, use -that catalog name instead of ``mysql`` in the above examples. +that catalog name instead of ``example`` in the above examples. .. _mysql-sql-support: @@ -332,7 +331,7 @@ For example, group and concatenate all employee IDs by manager ID:: * FROM TABLE( - mysql.system.query( + example.system.query( query => 'SELECT manager_id, GROUP_CONCAT(employee_id) FROM diff --git a/docs/src/main/sphinx/connector/oracle.rst b/docs/src/main/sphinx/connector/oracle.rst index a1a967853745..ade8f0b6642a 100644 --- a/docs/src/main/sphinx/connector/oracle.rst +++ b/docs/src/main/sphinx/connector/oracle.rst @@ -22,9 +22,9 @@ To connect to Oracle, you need: Configuration ------------- -To configure the Oracle connector as the ``oracle`` catalog, create a file named -``oracle.properties`` in ``etc/catalog``. Include the following connection -properties in the file: +To configure the Oracle connector as the ``example`` catalog, create a file +named ``example.properties`` in ``etc/catalog``. Include the following +connection properties in the file: .. code-block:: text @@ -103,10 +103,10 @@ The Oracle connector provides a schema for every Oracle database. Run ``SHOW SCHEMAS`` to see the available Oracle databases:: - SHOW SCHEMAS FROM oracle; + SHOW SCHEMAS FROM example; If you used a different name for your catalog properties file, use that catalog -name instead of ``oracle``. +name instead of ``example``. .. note:: The Oracle user must have access to the table in order to access it from Trino. @@ -119,17 +119,17 @@ Examples If you have an Oracle database named ``web``, run ``SHOW TABLES`` to see the tables it contains:: - SHOW TABLES FROM oracle.web; + SHOW TABLES FROM example.web; To see a list of the columns in the ``clicks`` table in the ``web`` database, run either of the following:: - DESCRIBE oracle.web.clicks; - SHOW COLUMNS FROM oracle.web.clicks; + DESCRIBE example.web.clicks; + SHOW COLUMNS FROM example.web.clicks; To access the clicks table in the web database, run the following:: - SELECT * FROM oracle.web.clicks; + SELECT * FROM example.web.clicks; .. _oracle-type-mapping: @@ -436,7 +436,7 @@ As a simple example, to select an entire table:: * FROM TABLE( - oracle.system.query( + example.system.query( query => 'SELECT * FROM @@ -454,7 +454,7 @@ As a practical example, you can use the sales FROM TABLE( - oracle.system.query( + example.system.query( query => 'SELECT * FROM diff --git a/docs/src/main/sphinx/connector/phoenix.rst b/docs/src/main/sphinx/connector/phoenix.rst index 66d85068e088..b16df3d3863f 100644 --- a/docs/src/main/sphinx/connector/phoenix.rst +++ b/docs/src/main/sphinx/connector/phoenix.rst @@ -23,7 +23,7 @@ Configuration ------------- To configure the Phoenix connector, create a catalog properties file -``etc/catalog/phoenix.properties`` with the following contents, +``etc/catalog/example.properties`` with the following contents, replacing ``host1,host2,host3`` with a comma-separated list of the ZooKeeper nodes used for discovery of the HBase cluster: @@ -72,25 +72,25 @@ Querying Phoenix tables The default empty schema in Phoenix maps to a schema named ``default`` in Trino. You can see the available Phoenix schemas by running ``SHOW SCHEMAS``:: - SHOW SCHEMAS FROM phoenix; + SHOW SCHEMAS FROM example; If you have a Phoenix schema named ``web``, you can view the tables in this schema by running ``SHOW TABLES``:: - SHOW TABLES FROM phoenix.web; + SHOW TABLES FROM example.web; You can see a list of the columns in the ``clicks`` table in the ``web`` schema using either of the following:: - DESCRIBE phoenix.web.clicks; - SHOW COLUMNS FROM phoenix.web.clicks; + DESCRIBE example.web.clicks; + SHOW COLUMNS FROM example.web.clicks; Finally, you can access the ``clicks`` table in the ``web`` schema:: - SELECT * FROM phoenix.web.clicks; + SELECT * FROM example.web.clicks; If you used a different name for your catalog properties file, use -that catalog name instead of ``phoenix`` in the above examples. +that catalog name instead of ``example`` in the above examples. .. _phoenix-type-mapping: @@ -216,7 +216,7 @@ Table properties - Phoenix Table property usage example:: - CREATE TABLE myschema.scientists ( + CREATE TABLE example_schema.scientists ( recordkey VARCHAR, birthday DATE, name VARCHAR, diff --git a/docs/src/main/sphinx/connector/pinot.rst b/docs/src/main/sphinx/connector/pinot.rst index e467cc4ff487..6a20df81af38 100644 --- a/docs/src/main/sphinx/connector/pinot.rst +++ b/docs/src/main/sphinx/connector/pinot.rst @@ -22,7 +22,7 @@ Configuration ------------- To configure the Pinot connector, create a catalog properties file -e.g. ``etc/catalog/pinot.properties`` with at least the following contents: +e.g. ``etc/catalog/example.properties`` with at least the following contents: .. code-block:: text @@ -115,12 +115,12 @@ Querying Pinot tables The Pinot connector automatically exposes all tables in the default schema of the catalog. You can list all tables in the pinot catalog with the following query:: - SHOW TABLES FROM pinot.default; + SHOW TABLES FROM example.default; You can list columns in the flight_status table:: - DESCRIBE pinot.default.flight_status; - SHOW COLUMNS FROM pinot.default.flight_status; + DESCRIBE example.default.flight_status; + SHOW COLUMNS FROM example.default.flight_status; Queries written with SQL are fully supported and can include filters and limits:: @@ -137,7 +137,7 @@ Filters and limits in the outer query are pushed down to Pinot. Let's look at an example query:: SELECT * - FROM pinot.default."SELECT MAX(col1), COUNT(col2) FROM pinot_table GROUP BY col3, col4" + FROM example.default."SELECT MAX(col1), COUNT(col2) FROM pinot_table GROUP BY col3, col4" WHERE col3 IN ('FOO', 'BAR') AND col4 > 50 LIMIT 30000 diff --git a/docs/src/main/sphinx/connector/postgresql.rst b/docs/src/main/sphinx/connector/postgresql.rst index b65a35905ae1..4d0797ecb3b7 100644 --- a/docs/src/main/sphinx/connector/postgresql.rst +++ b/docs/src/main/sphinx/connector/postgresql.rst @@ -27,9 +27,9 @@ The connector can query a database on a PostgreSQL server. Create a catalog properties file that specifies the PostgreSQL connector by setting the ``connector.name`` to ``postgresql``. -For example, to access a database as the ``postgresql`` catalog, create the -file ``etc/catalog/postgresql.properties``. Replace the connection properties -as appropriate for your setup: +For example, to access a database as the ``example`` catalog, create the file +``etc/catalog/example.properties``. Replace the connection properties as +appropriate for your setup: .. code-block:: text @@ -286,25 +286,25 @@ Querying PostgreSQL The PostgreSQL connector provides a schema for every PostgreSQL schema. You can see the available PostgreSQL schemas by running ``SHOW SCHEMAS``:: - SHOW SCHEMAS FROM postgresql; + SHOW SCHEMAS FROM example; If you have a PostgreSQL schema named ``web``, you can view the tables in this schema by running ``SHOW TABLES``:: - SHOW TABLES FROM postgresql.web; + SHOW TABLES FROM example.web; You can see a list of the columns in the ``clicks`` table in the ``web`` database using either of the following:: - DESCRIBE postgresql.web.clicks; - SHOW COLUMNS FROM postgresql.web.clicks; + DESCRIBE example.web.clicks; + SHOW COLUMNS FROM example.web.clicks; Finally, you can access the ``clicks`` table in the ``web`` schema:: - SELECT * FROM postgresql.web.clicks; + SELECT * FROM example.web.clicks; If you used a different name for your catalog properties file, use -that catalog name instead of ``postgresql`` in the above examples. +that catalog name instead of ``example`` in the above examples. .. _postgresql-sql-support: @@ -352,7 +352,7 @@ As a simple example, to select an entire table:: * FROM TABLE( - postgresql.system.query( + example.system.query( query => 'SELECT * FROM @@ -368,7 +368,7 @@ when using window functions:: * FROM TABLE( - postgresql.system.query( + example.system.query( query => 'SELECT *, array_agg(week) OVER ( diff --git a/docs/src/main/sphinx/connector/prometheus.rst b/docs/src/main/sphinx/connector/prometheus.rst index 5d379e872869..81c305f00d18 100644 --- a/docs/src/main/sphinx/connector/prometheus.rst +++ b/docs/src/main/sphinx/connector/prometheus.rst @@ -28,9 +28,8 @@ To query Prometheus, you need: Configuration ------------- -Create ``etc/catalog/prometheus.properties`` -to mount the Prometheus connector as the ``prometheus`` catalog, -replacing the properties as appropriate: +Create ``etc/catalog/example.properties`` to mount the Prometheus connector as +the ``example`` catalog, replacing the properties as appropriate: .. code-block:: text @@ -79,7 +78,7 @@ a relatively small window. For example: .. code-block:: sql - SELECT * FROM prometheus.default.up WHERE timestamp > (NOW() - INTERVAL '10' second); + SELECT * FROM example.default.up WHERE timestamp > (NOW() - INTERVAL '10' second); If the query does not include a WHERE clause limit, these config settings are meant to protect against an unlimited query. @@ -123,7 +122,7 @@ represented in Trino: .. code-block:: sql - SELECT * FROM prometheus.default.up; + SELECT * FROM example.default.up; .. code-block:: text diff --git a/docs/src/main/sphinx/connector/redis.rst b/docs/src/main/sphinx/connector/redis.rst index d4937341a870..ccacc8ecd8b0 100644 --- a/docs/src/main/sphinx/connector/redis.rst +++ b/docs/src/main/sphinx/connector/redis.rst @@ -29,8 +29,8 @@ Configuration ------------- To configure the Redis connector, create a catalog properties file -``etc/catalog/redis.properties`` with the following content, -replacing the properties as appropriate: +``etc/catalog/example.properties`` with the following content, replacing the +properties as appropriate: .. code-block:: text diff --git a/docs/src/main/sphinx/connector/redshift.rst b/docs/src/main/sphinx/connector/redshift.rst index d8d6aff497d9..d6409218144b 100644 --- a/docs/src/main/sphinx/connector/redshift.rst +++ b/docs/src/main/sphinx/connector/redshift.rst @@ -22,11 +22,11 @@ To connect to Redshift, you need: Configuration ------------- -To configure the Redshift connector, create a catalog properties file -in ``etc/catalog`` named, for example, ``redshift.properties``, to -mount the Redshift connector as the ``redshift`` catalog. -Create the file with the following contents, replacing the -connection properties as appropriate for your setup: +To configure the Redshift connector, create a catalog properties file in +``etc/catalog`` named, for example, ``example.properties``, to mount the +Redshift connector as the ``example`` catalog. Create the file with the +following contents, replacing the connection properties as appropriate for your +setup: .. code-block:: text @@ -94,25 +94,25 @@ Querying Redshift The Redshift connector provides a schema for every Redshift schema. You can see the available Redshift schemas by running ``SHOW SCHEMAS``:: - SHOW SCHEMAS FROM redshift; + SHOW SCHEMAS FROM example; If you have a Redshift schema named ``web``, you can view the tables in this schema by running ``SHOW TABLES``:: - SHOW TABLES FROM redshift.web; + SHOW TABLES FROM example.web; You can see a list of the columns in the ``clicks`` table in the ``web`` database using either of the following:: - DESCRIBE redshift.web.clicks; - SHOW COLUMNS FROM redshift.web.clicks; + DESCRIBE example.web.clicks; + SHOW COLUMNS FROM example.web.clicks; Finally, you can access the ``clicks`` table in the ``web`` schema:: - SELECT * FROM redshift.web.clicks; + SELECT * FROM example.web.clicks; -If you used a different name for your catalog properties file, use -that catalog name instead of ``redshift`` in the above examples. +If you used a different name for your catalog properties file, use that catalog +name instead of ``example`` in the above examples. .. _redshift-type-mapping: @@ -167,7 +167,7 @@ For example, select the top 10 nations by population:: * FROM TABLE( - redshift.system.query( + example.system.query( query => 'SELECT TOP 10 * FROM diff --git a/docs/src/main/sphinx/connector/sqlserver.rst b/docs/src/main/sphinx/connector/sqlserver.rst index 3f8d517824d0..f7773388fff0 100644 --- a/docs/src/main/sphinx/connector/sqlserver.rst +++ b/docs/src/main/sphinx/connector/sqlserver.rst @@ -27,8 +27,8 @@ The connector can query a single database on a given SQL Server instance. Create a catalog properties file that specifies the SQL server connector by setting the ``connector.name`` to ``sqlserver``. -For example, to access a database as ``sqlserver``, create the file -``etc/catalog/sqlserver.properties``. Replace the connection properties as +For example, to access a database as ``example``, create the file +``etc/catalog/example.properties``. Replace the connection properties as appropriate for your setup: .. code-block:: properties @@ -115,30 +115,31 @@ behavior of the connector and the issues queries to the database. Querying SQL Server ------------------- -The SQL Server connector provides access to all schemas visible to the specified user in the configured database. -For the following examples, assume the SQL Server catalog is ``sqlserver``. +The SQL Server connector provides access to all schemas visible to the specified +user in the configured database. For the following examples, assume the SQL +Server catalog is ``example``. You can see the available schemas by running ``SHOW SCHEMAS``:: - SHOW SCHEMAS FROM sqlserver; + SHOW SCHEMAS FROM example; If you have a schema named ``web``, you can view the tables in this schema by running ``SHOW TABLES``:: - SHOW TABLES FROM sqlserver.web; + SHOW TABLES FROM example.web; You can see a list of the columns in the ``clicks`` table in the ``web`` database using either of the following:: - DESCRIBE sqlserver.web.clicks; - SHOW COLUMNS FROM sqlserver.web.clicks; + DESCRIBE example.web.clicks; + SHOW COLUMNS FROM example.web.clicks; Finally, you can query the ``clicks`` table in the ``web`` schema:: - SELECT * FROM sqlserver.web.clicks; + SELECT * FROM example.web.clicks; If you used a different name for your catalog properties file, use -that catalog name instead of ``sqlserver`` in the above examples. +that catalog name instead of ``example`` in the above examples. .. _sqlserver-type-mapping: @@ -355,7 +356,7 @@ For example, select the top 10 percent of nations by population:: * FROM TABLE( - sqlserver.system.query( + example.system.query( query => 'SELECT TOP(10) PERCENT * FROM @@ -391,7 +392,7 @@ create them by executing the following statement in SQL Server Database. .. code-block:: sql - CREATE STATISTICS my_statistics_name ON table_schema.table_name (column_name); + CREATE STATISTICS example_statistics_name ON table_schema.table_name (column_name); SQL Server Database routinely updates the statistics. In some cases, you may want to force statistics update (e.g. after defining new column statistics or @@ -484,7 +485,7 @@ with the ``data_compression`` table property. Valid policies are ``NONE``, ``ROW Example:: - CREATE TABLE myschema.scientists ( + CREATE TABLE example_schema.scientists ( recordkey VARCHAR, name VARCHAR, age BIGINT, diff --git a/docs/src/main/sphinx/connector/table-redirection.fragment b/docs/src/main/sphinx/connector/table-redirection.fragment index ef8928fb3a2f..eb743737aa5c 100644 --- a/docs/src/main/sphinx/connector/table-redirection.fragment +++ b/docs/src/main/sphinx/connector/table-redirection.fragment @@ -10,9 +10,9 @@ Therefore, a metastore database can hold a variety of tables with different tabl As a concrete example, let's use the following simple scenario which makes use of table redirection:: - USE a-catalog.myschema; + USE example.example_schema; - EXPLAIN SELECT * FROM mytable; + EXPLAIN SELECT * FROM example_table; .. code-block:: text @@ -22,16 +22,16 @@ simple scenario which makes use of table redirection:: ... Output[columnNames = [...]] │ ... - └─ TableScan[table = another-catalog:myschema:mytable] + └─ TableScan[table = another_catalog:example_schema:example_table] ... The output of the ``EXPLAIN`` statement points out the actual -catalog which is handling the ``SELECT`` query over the table ``mytable``. +catalog which is handling the ``SELECT`` query over the table ``example_table``. The table redirection functionality works also when using fully qualified names for the tables:: - EXPLAIN SELECT * FROM a-catalog.myschema.mytable; + EXPLAIN SELECT * FROM example.example_schema.example_table; .. code-block:: text @@ -41,7 +41,7 @@ fully qualified names for the tables:: ... Output[columnNames = [...]] │ ... - └─ TableScan[table = another-catalog:myschema:mytable] + └─ TableScan[table = another_catalog:example_schema:example_table] ... Trino offers table redirection support for the following operations: diff --git a/docs/src/main/sphinx/release.rst b/docs/src/main/sphinx/release.rst index 29d8abe6168d..4f6355adfd74 100644 --- a/docs/src/main/sphinx/release.rst +++ b/docs/src/main/sphinx/release.rst @@ -2,6 +2,16 @@ Release notes ************* +.. _releases_2023: + +2023 +==== + +.. toctree:: + :maxdepth: 1 + + release/release-406 + .. _releases_2022: 2022 diff --git a/docs/src/main/sphinx/release/release-406.md b/docs/src/main/sphinx/release/release-406.md new file mode 100644 index 000000000000..2c313f160aa5 --- /dev/null +++ b/docs/src/main/sphinx/release/release-406.md @@ -0,0 +1,117 @@ +# Release 406 (25 Jan 2023) + +## General + +* Add support for [exchange spooling on HDFS](fte-exchange-hdfs) when + fault-tolerant execution is enabled. ({issue}`15160`) +* Add support for `CHECK` constraints in an `INSERT` statement. ({issue}`14964`) +* Improve planner estimates for queries containing outer joins over a subquery + involving `ORDER BY` and `LIMIT`. ({issue}`15428`) +* Improve accuracy of memory usage reporting for table scans. ({issue}`15711`) +* Improve performance of queries parsing date values in ISO 8601 format. ({issue}`15548`) +* Improve performance of queries with selective joins. ({issue}`15569`) +* Remove `legacy-phased` execution scheduler as an option for the + `query.execution-policy` configuration property. ({issue}`15657`) +* Fix failure when `WHERE` or `JOIN` clauses contain a `LIKE` expression with a + non-constant pattern or escape. ({issue}`15629`) +* Fix inaccurate planner estimates for queries with filters on columns without + statistics. ({issue}`15642`) +* Fix queries with outer joins failing when fault-tolerant execution is + enabled. ({issue}`15608`) +* Fix potential query failure when using `MATCH_RECOGNIZE`. ({issue}`15461`) +* Fix query failure when using group-based access control with column masks or + row filters. ({issue}`15583`) +* Fix potential hang during shutdown. ({issue}`15675`) +* Fix incorrect results when referencing a field resulting from the application + of a column mask expression that produces a `row` type. ({issue}`15659`) +* Fix incorrect application of column masks when a mask expression references a + different column in the underlying table. ({issue}`15680`) + +## BigQuery connector + +* Add support for [fault-tolerant execution](/admin/fault-tolerant-execution). ({issue}`15620`) +* Fix possible incorrect results for certain queries like `count(*)` when a + table has recently been written to. ({issue}`14981`) + +## Cassandra connector + +* Fix incorrect results when the Cassandra `list`, `map`, or `set` types contain + user-defined types. ({issue}`15771`) + +## Delta Lake connector + +* Reduce latency for `INSERT` queries on unpartitioned tables. ({issue}`15708`) +* Improve performance of reading Parquet files. ({issue}`15498`) +* Improve memory accounting of the Parquet reader. ({issue}`15554`) +* Improve performance of queries with filters or projections on low-cardinality + string columns stored in Parquet files. ({issue}`15269`) +* Fix reading more data than necessary from Parquet files for queries with + filters. ({issue}`15552`) +* Fix potential query failure when writing to Parquet from a table with an + `INTEGER` range on a `BIGINT` column. ({issue}`15496`) +* Fix query failure due to missing null counts in Parquet column indexes. ({issue}`15706`) + +## Hive connector + +* Add support for table redirections to catalogs using the Hudi connector. ({issue}`14750`) +* Reduce latency for `INSERT` queries on unpartitioned tables. ({issue}`15708`) +* Improve performance of caching. ({issue}`13243 `) +* Improve performance of reading Parquet files. ({issue}`15498`) +* Improve memory accounting of the Parquet reader. ({issue}`15554`) +* Improve performance of queries with filters or projections on low-cardinality + string columns stored in Parquet files. ({issue}`15269`) +* Improve performance of queries with filters when Bloom filter indexes are + present in Parquet files. Use of Bloom filters from Parquet files can be + disabled with the `parquet.use-bloom-filter` configuration property or the + `parquet_use_bloom_filter` session property. ({issue}`14428`) +* Allow coercion between Hive `UNIONTYPE` and Hive `STRUCT`-typed columns. ({issue}`15017`) +* Fix reading more data than necessary from Parquet files for queries with + filters. ({issue}`15552`) +* Fix query failure due to missing null counts in Parquet column indexes. ({issue}`15706`) +* Fix incorrect `schema already exists` error caused by a client timeout when + creating a new schema. ({issue}`15174`) + +## Hudi connector + +* Improve performance of reading Parquet files. ({issue}`15498`) +* Improve memory accounting of the Parquet reader. ({issue}`15554`) +* Improve performance of queries with filters or projections on low-cardinality + string columns stored in Parquet files. ({issue}`15269`) +* Fix reading more data than necessary from Parquet files for queries with + filters. ({issue}`15552`) +* Fix query failure due to missing null counts in Parquet column indexes. ({issue}`15706`) + +## Iceberg connector + +* Add support for changing column types. ({issue}`15515`) +* Add [support for the JDBC catalog](iceberg-jdbc-catalog). ({issue}`9968`) +* Reduce latency for `INSERT` queries on unpartitioned tables. ({issue}`15708`) +* Improve performance of reading Parquet files. ({issue}`15498`) +* Improve memory accounting of the Parquet reader. ({issue}`15554`) +* Improve performance of queries with filters or projections on low-cardinality + string columns stored in Parquet files. ({issue}`15269`) +* Fix reading more data than necessary from Parquet files for queries with + filters. ({issue}`15552`) +* Fix query failure due to missing null counts in Parquet column indexes. ({issue}`15706`) +* Fix query failure when a subquery contains [time travel](iceberg-time-travel). ({issue}`15607`) +* Fix failure when reading columns that had their type changed from `float` to + `double` by other query engines. ({issue}`15650`) +* Fix incorrect results when reading or writing `NaN` with `real` or `double` + types on partitioned columns. ({issue}`15723`) + +## MongoDB connector + +* Fix schemas not being dropped when trying to drop schemas with the + `mongodb.case-insensitive-name-matching` configuration property enabled. ({issue}`15716`) + +## PostgreSQL connector + +* Add support for changing column types. ({issue}`15515`) + +## SPI + +* Remove the `getDeleteRowIdColumnHandle()`, `beginDelete()`, `finishDelete()`, + `getUpdateRowIdColumnHandle()`, `beginUpdate()`, and `finishUpdate()` methods + from `ConnectorMetadata`. ({issue}`15161`) +* Remove the `UpdatablePageSource` interface. ({issue}`15161`) +* Remove support for multiple masks on a single column. ({issue}`15680`) diff --git a/docs/src/main/sphinx/security/internal-communication.rst b/docs/src/main/sphinx/security/internal-communication.rst index bf92ec1c7117..1aaea9fdb1cd 100644 --- a/docs/src/main/sphinx/security/internal-communication.rst +++ b/docs/src/main/sphinx/security/internal-communication.rst @@ -31,6 +31,27 @@ command: openssl rand 512 | base64 +.. _verify_secrets: + +Verify configuration +^^^^^^^^^^^^^^^^^^^^ + +To verify shared secret configuration: + +1. Start your Trino cluster with two or more nodes configured with a shared + secret. +2. Connect to the :doc:`Web UI `. +3. Confirm the number of ``ACTIVE WORKERS`` equals the number of nodes + configured with your shared secret. +4. Change the value of the shared secret on one worker, and restart the worker. +5. Log in to the Web UI and confirm the number of ``ACTIVE WORKERS`` is one + less. The worker with the invalid secret is not authenticated, and therefore + not registered with the coordinator. +6. Stop your Trino cluster, revert the value change on the worker, and restart + your cluster. +7. Confirm the number of ``ACTIVE WORKERS`` equals the number of nodes + configured with your shared secret. + Configure internal TLS ---------------------- diff --git a/docs/src/main/sphinx/security/overview.rst b/docs/src/main/sphinx/security/overview.rst index 2478aa73fe80..0c7cce75c81a 100644 --- a/docs/src/main/sphinx/security/overview.rst +++ b/docs/src/main/sphinx/security/overview.rst @@ -34,6 +34,8 @@ order of steps. Do not skip or combine steps. #. **Configure** a :doc:`a shared secret ` + :ref:`Verify this step is working correctly.` + #. **Enable authentication** * Start with :doc:`password file authentication ` to get up @@ -49,7 +51,7 @@ order of steps. Do not skip or combine steps. * Start with :doc:`file-based rules `. * Then configure another access control method as required. -:ref:`Verify this step is working correctly. ` + :ref:`Verify this step is working correctly. ` Configure one step at a time. Always restart the Trino server after each change, and verify the results before proceeding. diff --git a/lib/trino-array/pom.xml b/lib/trino-array/pom.xml index 6fd317dc3702..f8f04b39054b 100644 --- a/lib/trino-array/pom.xml +++ b/lib/trino-array/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-collect/pom.xml b/lib/trino-collect/pom.xml index d2f36ab8dd79..599401fad735 100644 --- a/lib/trino-collect/pom.xml +++ b/lib/trino-collect/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-collect/src/main/java/io/trino/collect/cache/EmptyCache.java b/lib/trino-collect/src/main/java/io/trino/collect/cache/EmptyCache.java index 0856264ea5d3..7ad5646fb24d 100644 --- a/lib/trino-collect/src/main/java/io/trino/collect/cache/EmptyCache.java +++ b/lib/trino-collect/src/main/java/io/trino/collect/cache/EmptyCache.java @@ -15,7 +15,9 @@ import com.google.common.cache.AbstractLoadingCache; import com.google.common.cache.CacheLoader; +import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import com.google.common.cache.CacheStats; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.UncheckedExecutionException; @@ -59,6 +61,33 @@ public V get(K key) return get(key, () -> loader.load(key)); } + @Override + public ImmutableMap getAll(Iterable keys) + throws ExecutionException + { + try { + Set keySet = ImmutableSet.copyOf(keys); + statsCounter.recordMisses(keySet.size()); + @SuppressWarnings("unchecked") // safe since all keys extend K + ImmutableMap result = (ImmutableMap) loader.loadAll(keySet); + for (K key : keySet) { + if (!result.containsKey(key)) { + throw new InvalidCacheLoadException("loadAll failed to return a value for " + key); + } + } + statsCounter.recordLoadSuccess(1); + return result; + } + catch (RuntimeException e) { + statsCounter.recordLoadException(1); + throw new UncheckedExecutionException(e); + } + catch (Exception e) { + statsCounter.recordLoadException(1); + throw new ExecutionException(e); + } + } + @Override public V get(K key, Callable valueLoader) throws ExecutionException diff --git a/lib/trino-filesystem/pom.xml b/lib/trino-filesystem/pom.xml index c718ad035563..1ccc6ca01265 100644 --- a/lib/trino-filesystem/pom.xml +++ b/lib/trino-filesystem/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-geospatial-toolkit/pom.xml b/lib/trino-geospatial-toolkit/pom.xml index e1b1f83a9966..16ae2eb8d36a 100644 --- a/lib/trino-geospatial-toolkit/pom.xml +++ b/lib/trino-geospatial-toolkit/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hadoop-toolkit/pom.xml b/lib/trino-hadoop-toolkit/pom.xml index 815c4d96dc22..84eca56a1fd6 100644 --- a/lib/trino-hadoop-toolkit/pom.xml +++ b/lib/trino-hadoop-toolkit/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index a908ed991e2c..df71424ee285 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml @@ -98,7 +98,20 @@ jmxutils + + + io.airlift + concurrent + runtime + + + + io.trino + trino-testing-services + test + + io.airlift testing @@ -111,6 +124,18 @@ test + + org.openjdk.jmh + jmh-core + test + + + + org.openjdk.jmh + jmh-generator-annprocess + test + + org.testng testng diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCache.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCache.java index b36936b117da..246364a91880 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCache.java +++ b/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCache.java @@ -14,7 +14,6 @@ package io.trino.hdfs; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import io.airlift.log.Logger; import org.apache.hadoop.conf.Configuration; @@ -36,21 +35,20 @@ import org.apache.hadoop.util.ReflectionUtils; import org.gaul.modernizer_maven_annotations.SuppressModernizer; -import javax.annotation.concurrent.GuardedBy; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.util.EnumSet; -import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Strings.nullToEmpty; +import static com.google.common.base.Throwables.throwIfInstanceOf; import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; @@ -70,17 +68,20 @@ public class TrinoFileSystemCache private final TrinoFileSystemCacheStats stats; - @GuardedBy("this") - private final Map map = new HashMap<>(); + private final Map cache = new ConcurrentHashMap<>(); + /* + * ConcurrentHashMap has a lock per partitioned key-space bucket, and hence there is no consistent + * or 'serialized' view of current number of entries in the map from a thread that would like to + * add/delete/update an entry. As we have to limit the max size of the cache to 'fs.cache.max-size', + * an auxiliary variable `cacheSize` is used to track the 'serialized' view of entry count in the cache. + * cacheSize should only be updated after acquiring cache's partition lock (eg: from inside cache.compute()) + */ + private final AtomicLong cacheSize = new AtomicLong(); @VisibleForTesting TrinoFileSystemCache() { - this.stats = new TrinoFileSystemCacheStats(() -> { - synchronized (this) { - return map.size(); - } - }); + this.stats = new TrinoFileSystemCacheStats(cache::size); } @Override @@ -102,55 +103,46 @@ public FileSystem getUnique(URI uri, Configuration conf) @VisibleForTesting int getCacheSize() { - return map.size(); + return cache.size(); } - private synchronized FileSystem getInternal(URI uri, Configuration conf, long unique) + private FileSystem getInternal(URI uri, Configuration conf, long unique) throws IOException { UserGroupInformation userGroupInformation = UserGroupInformation.getCurrentUser(); FileSystemKey key = createFileSystemKey(uri, userGroupInformation, unique); Set privateCredentials = getPrivateCredentials(userGroupInformation); - FileSystemHolder fileSystemHolder = map.get(key); - if (fileSystemHolder == null) { - int maxSize = conf.getInt("fs.cache.max-size", 1000); - if (map.size() >= maxSize) { - stats.newGetCallFailed(); - throw new IOException(format("FileSystem max cache size has been reached: %s", maxSize)); - } - try { - FileSystem fileSystem = createFileSystem(uri, conf); - fileSystemHolder = new FileSystemHolder(fileSystem, privateCredentials); - map.put(key, fileSystemHolder); - } - catch (IOException e) { - stats.newGetCallFailed(); - throw e; - } + int maxSize = conf.getInt("fs.cache.max-size", 1000); + FileSystemHolder fileSystemHolder; + try { + fileSystemHolder = cache.compute(key, (k, currentFileSystemHolder) -> { + if (currentFileSystemHolder == null) { + if (cacheSize.getAndUpdate(currentSize -> Math.min(currentSize + 1, maxSize)) >= maxSize) { + throw new RuntimeException( + new IOException(format("FileSystem max cache size has been reached: %s", maxSize))); + } + return new FileSystemHolder(conf, privateCredentials); + } + else { + // Update file system instance when credentials change. + if (currentFileSystemHolder.credentialsChanged(uri, conf, privateCredentials)) { + return new FileSystemHolder(conf, privateCredentials); + } + else { + return currentFileSystemHolder; + } + } + }); + + // Now create the filesystem object outside of cache's lock + fileSystemHolder.createFileSystemOnce(uri, conf); } - - // Update file system instance when credentials change. - // - Private credentials are only set when using Kerberos authentication. - // When the user is the same, but the private credentials are different, - // that means that Kerberos ticket has expired and re-login happened. - // To prevent cache leak in such situation, the privateCredentials are not - // a part of the FileSystemKey, but part of the FileSystemHolder. When a - // Kerberos re-login occurs, re-create the file system and cache it using - // the same key. - // - Extra credentials are used to authenticate with certain file systems. - if ((isHdfs(uri) && !fileSystemHolder.getPrivateCredentials().equals(privateCredentials)) || - extraCredentialsChanged(fileSystemHolder.getFileSystem(), conf)) { - map.remove(key); - try { - FileSystem fileSystem = createFileSystem(uri, conf); - fileSystemHolder = new FileSystemHolder(fileSystem, privateCredentials); - map.put(key, fileSystemHolder); - } - catch (IOException e) { - stats.newGetCallFailed(); - throw e; - } + catch (RuntimeException | IOException e) { + stats.newGetCallFailed(); + throwIfInstanceOf(e, IOException.class); + throwIfInstanceOf(e.getCause(), IOException.class); + throw e; } return fileSystemHolder.getFileSystem(); @@ -178,20 +170,54 @@ private static FileSystem createFileSystem(URI uri, Configuration conf) } @Override - public synchronized void remove(FileSystem fileSystem) + public void remove(FileSystem fileSystem) { stats.newRemoveCall(); - map.values().removeIf(holder -> holder.getFileSystem().equals(fileSystem)); + cache.forEach((key, fileSystemHolder) -> { + if (fileSystem.equals(fileSystemHolder.getFileSystem())) { + // After acquiring the lock, decrement cacheSize only if + // (1) the key is still mapped to a FileSystemHolder + // (2) the filesystem object inside FileSystemHolder is the same + cache.compute(key, (k, currentFileSystemHolder) -> { + if (currentFileSystemHolder != null + && fileSystem.equals(currentFileSystemHolder.getFileSystem())) { + cacheSize.decrementAndGet(); + return null; + } + return currentFileSystemHolder; + }); + } + }); } @Override - public synchronized void closeAll() + public void closeAll() throws IOException { - for (FileSystemHolder fileSystemHolder : ImmutableList.copyOf(map.values())) { - closeFileSystem(fileSystemHolder.getFileSystem()); + try { + cache.forEach((key, fileSystemHolder) -> { + try { + cache.compute(key, (k, currentFileSystemHolder) -> { + // decrement cacheSize only if the key is still mapped + if (currentFileSystemHolder != null) { + cacheSize.decrementAndGet(); + } + return null; + }); + FileSystem fs = fileSystemHolder.getFileSystem(); + if (fs != null) { + closeFileSystem(fs); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + catch (RuntimeException e) { + throwIfInstanceOf(e.getCause(), IOException.class); + throw e; } - map.clear(); } @SuppressModernizer @@ -245,12 +271,6 @@ private static boolean isHdfs(URI uri) return "hdfs".equals(scheme) || "viewfs".equals(scheme); } - private static boolean extraCredentialsChanged(FileSystem fileSystem, Configuration configuration) - { - return !configuration.get(CACHE_KEY, "").equals( - fileSystem.getConf().get(CACHE_KEY, "")); - } - private static class FileSystemKey { private final String scheme; @@ -306,23 +326,45 @@ public String toString() private static class FileSystemHolder { - private final FileSystem fileSystem; private final Set privateCredentials; + private final String cacheCredentials; + private volatile FileSystem fileSystem; - public FileSystemHolder(FileSystem fileSystem, Set privateCredentials) + public FileSystemHolder(Configuration conf, Set privateCredentials) { - this.fileSystem = requireNonNull(fileSystem, "fileSystem is null"); this.privateCredentials = ImmutableSet.copyOf(requireNonNull(privateCredentials, "privateCredentials is null")); + this.cacheCredentials = conf.get(CACHE_KEY, ""); } - public FileSystem getFileSystem() + public void createFileSystemOnce(URI uri, Configuration conf) + throws IOException { - return fileSystem; + if (fileSystem == null) { + synchronized (this) { + if (fileSystem == null) { + fileSystem = TrinoFileSystemCache.createFileSystem(uri, conf); + } + } + } } - public Set getPrivateCredentials() + public boolean credentialsChanged(URI newUri, Configuration newConf, Set newPrivateCredentials) { - return privateCredentials; + // - Private credentials are only set when using Kerberos authentication. + // When the user is the same, but the private credentials are different, + // that means that Kerberos ticket has expired and re-login happened. + // To prevent cache leak in such situation, the privateCredentials are not + // a part of the FileSystemKey, but part of the FileSystemHolder. When a + // Kerberos re-login occurs, re-create the file system and cache it using + // the same key. + // - Extra credentials are used to authenticate with certain file systems. + return (isHdfs(newUri) && !this.privateCredentials.equals(newPrivateCredentials)) + || !this.cacheCredentials.equals(newConf.get(CACHE_KEY, "")); + } + + public FileSystem getFileSystem() + { + return fileSystem; } @Override @@ -331,6 +373,7 @@ public String toString() return toStringHelper(this) .add("fileSystem", fileSystem) .add("privateCredentials", privateCredentials) + .add("cacheCredentials", cacheCredentials) .toString(); } } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/BenchmarkGetFileSystem.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/BenchmarkGetFileSystem.java new file mode 100644 index 000000000000..457a23c70b6a --- /dev/null +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/BenchmarkGetFileSystem.java @@ -0,0 +1,103 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.hdfs; + +import io.trino.jmh.Benchmarks; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.SplittableRandom; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static io.airlift.concurrent.MoreFutures.getFutureValue; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 5000, timeUnit = MILLISECONDS) +@Fork(2) +@Measurement(iterations = 5, time = 5000, timeUnit = MILLISECONDS) +@BenchmarkMode(Mode.AverageTime) +public class BenchmarkGetFileSystem +{ + @State(Scope.Thread) + public static class BenchmarkData + { + @Param({"10", "100", "1000"}) + private int userCount; + + @Param({"1", "16"}) + private int threadCount; + + @Param("1000") + private int getCallsPerInvocation; + + public List> callableTasks; + public ExecutorService executor; + public Blackhole blackhole; + + @Setup(Level.Invocation) + public void setUp(Blackhole blackhole) + { + this.blackhole = blackhole; + + this.callableTasks = new ArrayList<>(); + for (int i = 0; i < threadCount; i++) { + this.callableTasks.add(new TestFileSystemCache.CreateFileSystemsAndConsume( + new SplittableRandom(i), userCount, getCallsPerInvocation, fs -> {})); + } + + this.executor = Executors.newFixedThreadPool(threadCount); + } + + @TearDown(Level.Invocation) + public void tearDown() + throws IOException + { + TrinoFileSystemCache.INSTANCE.closeAll(); + executor.shutdownNow(); + } + } + + @Benchmark + public void benchmark(BenchmarkData data) + throws InterruptedException, ExecutionException + { + data.executor.invokeAll(data.callableTasks).forEach(f -> data.blackhole.consume(getFutureValue(f))); + } + + public static void main(String[] args) + throws Exception + { + Benchmarks.benchmark(BenchmarkGetFileSystem.class).run(); + } +} diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java index 0e1f7a054daa..fa1fbd14b596 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java @@ -70,6 +70,7 @@ public void tearDown() closeAll( () -> fs.delete(new Path(tempRoot.toURI()), true), fs); + fs = null; } @Test diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java index e8c003da8666..93d39462e48c 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java @@ -14,22 +14,45 @@ package io.trino.hdfs; import com.google.common.collect.ImmutableSet; +import io.airlift.concurrent.MoreFutures; import io.trino.hdfs.authentication.ImpersonatingHdfsAuthentication; import io.trino.hdfs.authentication.SimpleHadoopAuthentication; import io.trino.hdfs.authentication.SimpleUserNameProvider; import io.trino.spi.security.ConnectorIdentity; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.gaul.modernizer_maven_annotations.SuppressModernizer; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.SplittableRandom; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotSame; import static org.testng.Assert.assertSame; +@Test(singleThreaded = true) public class TestFileSystemCache { + @BeforeMethod(alwaysRun = true) + @AfterClass(alwaysRun = true) + public void cleanup() + throws IOException + { + FileSystem.closeAll(); + } + @Test public void testFileSystemCache() throws IOException @@ -56,9 +79,98 @@ public void testFileSystemCache() assertNotSame(fs5, fs1); } - private FileSystem getFileSystem(HdfsEnvironment environment, ConnectorIdentity identity) + @Test + public void testFileSystemCacheException() throws IOException + { + HdfsEnvironment environment = new HdfsEnvironment( + new DynamicHdfsConfiguration(new HdfsConfigurationInitializer(new HdfsConfig()), ImmutableSet.of()), + new HdfsConfig(), + new ImpersonatingHdfsAuthentication(new SimpleHadoopAuthentication(), new SimpleUserNameProvider())); + + int maxCacheSize = 1000; + for (int i = 0; i < maxCacheSize; i++) { + assertEquals(TrinoFileSystemCache.INSTANCE.getFileSystemCacheStats().getCacheSize(), i); + getFileSystem(environment, ConnectorIdentity.ofUser("user" + i)); + } + assertEquals(TrinoFileSystemCache.INSTANCE.getFileSystemCacheStats().getCacheSize(), maxCacheSize); + assertThatThrownBy(() -> getFileSystem(environment, ConnectorIdentity.ofUser("user" + maxCacheSize))) + .isInstanceOf(IOException.class) + .hasMessage("FileSystem max cache size has been reached: " + maxCacheSize); + } + + @Test + public void testFileSystemCacheConcurrency() throws InterruptedException, ExecutionException, IOException + { + int numThreads = 20; + List> callableTasks = new ArrayList<>(); + for (int i = 0; i < numThreads; i++) { + callableTasks.add( + new CreateFileSystemsAndConsume( + new SplittableRandom(i), + 10, + 1000, + new FileSystemCloser())); + } + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + + assertEquals(TrinoFileSystemCache.INSTANCE.getFileSystemCacheStats().getCacheSize(), 0); + executor.invokeAll(callableTasks).forEach(MoreFutures::getFutureValue); + executor.shutdown(); + assertEquals(TrinoFileSystemCache.INSTANCE.getFileSystemCacheStats().getCacheSize(), 0, "Cache size is non zero"); + } + + private static FileSystem getFileSystem(HdfsEnvironment environment, ConnectorIdentity identity) throws IOException { return environment.getFileSystem(identity, new Path("/"), newEmptyConfiguration()); } + + @FunctionalInterface + public interface FileSystemConsumer + { + void consume(FileSystem fileSystem) throws IOException; + } + + private static class FileSystemCloser + implements FileSystemConsumer + { + @Override + @SuppressModernizer + public void consume(FileSystem fileSystem) throws IOException + { + fileSystem.close(); /* triggers fscache.remove() */ + } + } + + public static class CreateFileSystemsAndConsume + implements Callable + { + private final SplittableRandom random; + private final int userCount; + private final int getCallsPerInvocation; + private final FileSystemConsumer consumer; + + private static final HdfsEnvironment environment = new HdfsEnvironment( + new DynamicHdfsConfiguration(new HdfsConfigurationInitializer(new HdfsConfig()), ImmutableSet.of()), + new HdfsConfig(), + new ImpersonatingHdfsAuthentication(new SimpleHadoopAuthentication(), new SimpleUserNameProvider())); + + CreateFileSystemsAndConsume(SplittableRandom random, int numUsers, int numGetCallsPerInvocation, FileSystemConsumer consumer) + { + this.random = requireNonNull(random, "random is null"); + this.userCount = numUsers; + this.getCallsPerInvocation = numGetCallsPerInvocation; + this.consumer = consumer; + } + + @Override + public Void call() throws IOException + { + for (int i = 0; i < getCallsPerInvocation; i++) { + FileSystem fs = getFileSystem(environment, ConnectorIdentity.ofUser("user" + random.nextInt(userCount))); + consumer.consume(fs); + } + return null; + } + } } diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index 51ce4af0e186..4d7ff960ef9a 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index 6e6bd072b1ee..1309508d132f 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 8e3162d4f09f..e42d13b6952b 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java b/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java index d0cbb3e7bb5b..2db21fc19d9a 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java @@ -1348,9 +1348,9 @@ private static Type rowType(Type... fieldTypes) { ImmutableList.Builder typeSignatureParameters = ImmutableList.builder(); for (int i = 0; i < fieldTypes.length; i++) { - String filedName = "field_" + i; + String fieldName = "field_" + i; Type fieldType = fieldTypes[i]; - typeSignatureParameters.add(TypeSignatureParameter.namedTypeParameter(new NamedTypeSignature(Optional.of(new RowFieldName(filedName)), fieldType.getTypeSignature()))); + typeSignatureParameters.add(TypeSignatureParameter.namedTypeParameter(new NamedTypeSignature(Optional.of(new RowFieldName(fieldName)), fieldType.getTypeSignature()))); } return TESTING_TYPE_MANAGER.getParameterizedType(StandardTypes.ROW, typeSignatureParameters.build()); } diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index fb10d91d69df..743568dce35a 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetReaderUtils.java b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetReaderUtils.java index 1de656413ac8..17b457cb884b 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetReaderUtils.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetReaderUtils.java @@ -152,6 +152,14 @@ public static byte castToByteNegate(boolean value) return (byte) (value ? 0 : 1); } + public static short toShortExact(long value) + { + if ((short) value != value) { + throw new ArithmeticException("short overflow"); + } + return (short) value; + } + public static short toShortExact(int value) { if ((short) value != value) { @@ -160,6 +168,14 @@ public static short toShortExact(int value) return (short) value; } + public static byte toByteExact(long value) + { + if ((byte) value != value) { + throw new ArithmeticException("byte overflow"); + } + return (byte) value; + } + public static byte toByteExact(int value) { if ((byte) value != value) { diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetTypeUtils.java b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetTypeUtils.java index c95d5ffcf5b9..71234980612f 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetTypeUtils.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetTypeUtils.java @@ -288,7 +288,7 @@ public static long getShortDecimalValue(byte[] bytes, int startOffset, int lengt return value; } - public static void checkBytesFitInShortDecimal(byte[] bytes, int offset, int length, Type trinoType, ColumnDescriptor descriptor) + public static void checkBytesFitInShortDecimal(byte[] bytes, int offset, int length, ColumnDescriptor descriptor) { int endOffset = offset + length; // Equivalent to expectedValue = bytes[endOffset] < 0 ? -1 : 0 @@ -296,9 +296,8 @@ public static void checkBytesFitInShortDecimal(byte[] bytes, int offset, int len for (int i = offset; i < endOffset; i++) { if (bytes[i] != expectedValue) { throw new TrinoException(NOT_SUPPORTED, format( - "Could not read unscaled value %s into %s from column %s", + "Could not read unscaled value %s into a short decimal from column %s", new BigInteger(bytes, offset, length + Long.BYTES), - trinoType, descriptor)); } } diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ColumnReaderFactory.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ColumnReaderFactory.java index 027042572dec..6d7ccb16964a 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ColumnReaderFactory.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ColumnReaderFactory.java @@ -25,6 +25,7 @@ import io.trino.spi.type.AbstractVariableWidthType; import io.trino.spi.type.CharType; import io.trino.spi.type.DecimalType; +import io.trino.spi.type.Decimals; import io.trino.spi.type.TimeType; import io.trino.spi.type.TimestampType; import io.trino.spi.type.TimestampWithTimeZoneType; @@ -78,6 +79,8 @@ public final class ColumnReaderFactory { + private static final int MAX_INT_DIGITS = 10; + private ColumnReaderFactory() {} public static ColumnReader create(PrimitiveField field, DateTimeZone timeZone, AggregatedMemoryContext aggregatedMemoryContext, boolean useBatchedColumnReaders) @@ -90,17 +93,23 @@ public static ColumnReader create(PrimitiveField field, DateTimeZone timeZone, A if (BOOLEAN.equals(type) && primitiveType == PrimitiveTypeName.BOOLEAN) { return new FlatColumnReader<>(field, ValueDecoders::getBooleanDecoder, BOOLEAN_ADAPTER, memoryContext); } - if (TINYINT.equals(type) && primitiveType == INT32) { - if (isIntegerAnnotation(annotation)) { - return new FlatColumnReader<>(field, ValueDecoders::getByteDecoder, BYTE_ADAPTER, memoryContext); + if (TINYINT.equals(type) && isIntegerOrDecimalPrimitive(primitiveType)) { + if (isZeroScaleShortDecimalAnnotation(annotation)) { + return new FlatColumnReader<>(field, TransformingValueDecoders::getShortDecimalToByteDecoder, BYTE_ADAPTER, memoryContext); } - throw unsupportedException(type, field); + if (!isIntegerAnnotationAndPrimitive(annotation, primitiveType)) { + throw unsupportedException(type, field); + } + return new FlatColumnReader<>(field, ValueDecoders::getByteDecoder, BYTE_ADAPTER, memoryContext); } - if (SMALLINT.equals(type) && primitiveType == INT32) { - if (isIntegerAnnotation(annotation)) { - return new FlatColumnReader<>(field, ValueDecoders::getShortDecoder, SHORT_ADAPTER, memoryContext); + if (SMALLINT.equals(type) && isIntegerOrDecimalPrimitive(primitiveType)) { + if (isZeroScaleShortDecimalAnnotation(annotation)) { + return new FlatColumnReader<>(field, TransformingValueDecoders::getShortDecimalToShortDecoder, SHORT_ADAPTER, memoryContext); } - throw unsupportedException(type, field); + if (!isIntegerAnnotationAndPrimitive(annotation, primitiveType)) { + throw unsupportedException(type, field); + } + return new FlatColumnReader<>(field, ValueDecoders::getShortDecoder, SHORT_ADAPTER, memoryContext); } if (DATE.equals(type) && primitiveType == INT32) { if (annotation == null || annotation instanceof DateLogicalTypeAnnotation) { @@ -108,17 +117,14 @@ public static ColumnReader create(PrimitiveField field, DateTimeZone timeZone, A } throw unsupportedException(type, field); } - if (type instanceof AbstractIntType && primitiveType == INT32) { - if (isIntegerAnnotation(annotation)) { - return new FlatColumnReader<>(field, ValueDecoders::getIntDecoder, INT_ADAPTER, memoryContext); + if (type instanceof AbstractIntType && isIntegerOrDecimalPrimitive(primitiveType)) { + if (isZeroScaleShortDecimalAnnotation(annotation)) { + return new FlatColumnReader<>(field, TransformingValueDecoders::getShortDecimalToIntDecoder, INT_ADAPTER, memoryContext); } - throw unsupportedException(type, field); - } - if (type instanceof AbstractLongType && primitiveType == INT32) { - if (isIntegerAnnotation(annotation)) { - return new FlatColumnReader<>(field, ValueDecoders::getIntToLongDecoder, LONG_ADAPTER, memoryContext); + if (!isIntegerAnnotationAndPrimitive(annotation, primitiveType)) { + throw unsupportedException(type, field); } - throw unsupportedException(type, field); + return new FlatColumnReader<>(field, ValueDecoders::getIntDecoder, INT_ADAPTER, memoryContext); } if (type instanceof TimeType && primitiveType == INT64) { if (annotation instanceof TimeLogicalTypeAnnotation timeAnnotation && timeAnnotation.getUnit() == MICROS) { @@ -126,14 +132,22 @@ public static ColumnReader create(PrimitiveField field, DateTimeZone timeZone, A } throw unsupportedException(type, field); } - if (type instanceof AbstractLongType && primitiveType == INT64) { - if (BIGINT.equals(type) && annotation instanceof TimestampLogicalTypeAnnotation) { - return new FlatColumnReader<>(field, ValueDecoders::getLongDecoder, LONG_ADAPTER, memoryContext); + if (BIGINT.equals(type) && primitiveType == INT64 && annotation instanceof TimestampLogicalTypeAnnotation) { + return new FlatColumnReader<>(field, ValueDecoders::getLongDecoder, LONG_ADAPTER, memoryContext); + } + if (type instanceof AbstractLongType && isIntegerOrDecimalPrimitive(primitiveType)) { + if (isZeroScaleShortDecimalAnnotation(annotation)) { + return new FlatColumnReader<>(field, ValueDecoders::getShortDecimalDecoder, LONG_ADAPTER, memoryContext); + } + if (!isIntegerAnnotationAndPrimitive(annotation, primitiveType)) { + throw unsupportedException(type, field); + } + if (primitiveType == INT32) { + return new FlatColumnReader<>(field, ValueDecoders::getIntToLongDecoder, LONG_ADAPTER, memoryContext); } - if (isIntegerAnnotation(annotation)) { + if (primitiveType == INT64) { return new FlatColumnReader<>(field, ValueDecoders::getLongDecoder, LONG_ADAPTER, memoryContext); } - throw unsupportedException(type, field); } if (REAL.equals(type) && primitiveType == FLOAT) { return new FlatColumnReader<>(field, ValueDecoders::getRealDecoder, INT_ADAPTER, memoryContext); @@ -200,16 +214,29 @@ public static ColumnReader create(PrimitiveField field, DateTimeZone timeZone, A }; } if (type instanceof DecimalType decimalType && decimalType.isShort() - && (primitiveType == INT32 || primitiveType == INT64 || primitiveType == FIXED_LEN_BYTE_ARRAY)) { - if (annotation instanceof DecimalLogicalTypeAnnotation decimalAnnotation && !isDecimalRescaled(decimalAnnotation, decimalType)) { - return new FlatColumnReader<>(field, ValueDecoders::getShortDecimalDecoder, LONG_ADAPTER, memoryContext); + && isIntegerOrDecimalPrimitive(primitiveType)) { + if (decimalType.getScale() == 0 && decimalType.getPrecision() >= MAX_INT_DIGITS + && primitiveType == INT32 + && isIntegerAnnotation(annotation)) { + return new FlatColumnReader<>(field, ValueDecoders::getIntToLongDecoder, LONG_ADAPTER, memoryContext); + } + if (!(annotation instanceof DecimalLogicalTypeAnnotation decimalAnnotation)) { + throw unsupportedException(type, field); + } + if (isDecimalRescaled(decimalAnnotation, decimalType)) { + return new FlatColumnReader<>(field, TransformingValueDecoders::getRescaledShortDecimalDecoder, LONG_ADAPTER, memoryContext); } + return new FlatColumnReader<>(field, ValueDecoders::getShortDecimalDecoder, LONG_ADAPTER, memoryContext); } if (type instanceof DecimalType decimalType && !decimalType.isShort() - && (primitiveType == BINARY || primitiveType == FIXED_LEN_BYTE_ARRAY)) { - if (annotation instanceof DecimalLogicalTypeAnnotation decimalAnnotation && !isDecimalRescaled(decimalAnnotation, decimalType)) { - return new FlatColumnReader<>(field, ValueDecoders::getLongDecimalDecoder, INT128_ADAPTER, memoryContext); + && isIntegerOrDecimalPrimitive(primitiveType)) { + if (!(annotation instanceof DecimalLogicalTypeAnnotation decimalAnnotation)) { + throw unsupportedException(type, field); + } + if (isDecimalRescaled(decimalAnnotation, decimalType)) { + return new FlatColumnReader<>(field, TransformingValueDecoders::getRescaledLongDecimalDecoder, INT128_ADAPTER, memoryContext); } + return new FlatColumnReader<>(field, ValueDecoders::getLongDecimalDecoder, INT128_ADAPTER, memoryContext); } if (type instanceof VarcharType varcharType && !varcharType.isUnbounded() && primitiveType == BINARY) { return new FlatColumnReader<>(field, ValueDecoders::getBoundedVarcharBinaryDecoder, BINARY_ADAPTER, memoryContext); @@ -228,6 +255,9 @@ public static ColumnReader create(PrimitiveField field, DateTimeZone timeZone, A } throw unsupportedException(type, field); } + throw new TrinoException( + NOT_SUPPORTED, + format("Reading Trino column (%s) from Parquet column (%s) is not supported by optimized parquet reader", type, field.getDescriptor())); } return switch (primitiveType) { @@ -304,13 +334,27 @@ private static boolean isDecimalRescaled(DecimalLogicalTypeAnnotation decimalAnn private static boolean isIntegerAnnotation(LogicalTypeAnnotation typeAnnotation) { - return typeAnnotation == null || typeAnnotation instanceof IntLogicalTypeAnnotation || isZeroScaleDecimalAnnotation(typeAnnotation); + return typeAnnotation == null || typeAnnotation instanceof IntLogicalTypeAnnotation; + } + + private static boolean isZeroScaleShortDecimalAnnotation(LogicalTypeAnnotation typeAnnotation) + { + return typeAnnotation instanceof DecimalLogicalTypeAnnotation decimalAnnotation + && decimalAnnotation.getScale() == 0 + && decimalAnnotation.getPrecision() <= Decimals.MAX_SHORT_PRECISION; + } + + private static boolean isIntegerOrDecimalPrimitive(PrimitiveTypeName primitiveType) + { + // Integers may be stored in INT32 or INT64 + // Decimals may be stored as INT32, INT64, BINARY or FIXED_LEN_BYTE_ARRAY + // Short decimals with zero scale in parquet files may be read as integers in Trino + return primitiveType == INT32 || primitiveType == INT64 || primitiveType == BINARY || primitiveType == FIXED_LEN_BYTE_ARRAY; } - private static boolean isZeroScaleDecimalAnnotation(LogicalTypeAnnotation typeAnnotation) + private static boolean isIntegerAnnotationAndPrimitive(LogicalTypeAnnotation typeAnnotation, PrimitiveTypeName primitiveType) { - return typeAnnotation instanceof DecimalLogicalTypeAnnotation - && ((DecimalLogicalTypeAnnotation) typeAnnotation).getScale() == 0; + return isIntegerAnnotation(typeAnnotation) && (primitiveType == INT32 || primitiveType == INT64); } private static TrinoException unsupportedException(Type type, PrimitiveField field) diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ShortDecimalColumnReader.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ShortDecimalColumnReader.java index ce7da276d923..ece8a0352fd9 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ShortDecimalColumnReader.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ShortDecimalColumnReader.java @@ -74,7 +74,7 @@ else if (field.getDescriptor().getPrimitiveType().getPrimitiveTypeName() == INT6 } else { int startOffset = bytes.length - Long.BYTES; - checkBytesFitInShortDecimal(bytes, 0, startOffset, trinoType, field.getDescriptor()); + checkBytesFitInShortDecimal(bytes, 0, startOffset, field.getDescriptor()); value = getShortDecimalValue(bytes, startOffset, Long.BYTES); } } diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ApacheParquetValueDecoders.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ApacheParquetValueDecoders.java index 5fb1bf2fe590..a5d4701f8e35 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ApacheParquetValueDecoders.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ApacheParquetValueDecoders.java @@ -20,13 +20,14 @@ import io.trino.plugin.base.type.DecodedTimestamp; import io.trino.spi.type.CharType; import io.trino.spi.type.Chars; -import io.trino.spi.type.DecimalType; +import io.trino.spi.type.Decimals; import io.trino.spi.type.Int128; import io.trino.spi.type.VarcharType; import io.trino.spi.type.Varchars; import org.apache.parquet.bytes.ByteBufferInputStream; import org.apache.parquet.column.ColumnDescriptor; import org.apache.parquet.column.values.ValuesReader; +import org.apache.parquet.schema.LogicalTypeAnnotation; import java.io.IOException; import java.io.UncheckedIOException; @@ -45,6 +46,7 @@ import static io.trino.parquet.reader.flat.Int96ColumnAdapter.Int96Buffer; import static io.trino.spi.type.Varchars.truncateToLength; import static java.util.Objects.requireNonNull; +import static org.apache.parquet.schema.LogicalTypeAnnotation.DecimalLogicalTypeAnnotation; /** * This is a set of proxy value decoders that use a delegated value reader from apache lib. @@ -252,18 +254,21 @@ public static final class ShortDecimalApacheParquetValueDecoder implements ValueDecoder { private final ValuesReader delegate; - private final DecimalType decimalType; private final ColumnDescriptor descriptor; private final int typeLength; - public ShortDecimalApacheParquetValueDecoder(ValuesReader delegate, DecimalType decimalType, ColumnDescriptor descriptor) + public ShortDecimalApacheParquetValueDecoder(ValuesReader delegate, ColumnDescriptor descriptor) { this.delegate = requireNonNull(delegate, "delegate is null"); - checkArgument(decimalType.isShort(), "Decimal type %s is not a short decimal", decimalType); - this.decimalType = decimalType; - this.descriptor = requireNonNull(descriptor, "descriptor is null"); + LogicalTypeAnnotation logicalTypeAnnotation = descriptor.getPrimitiveType().getLogicalTypeAnnotation(); + checkArgument( + logicalTypeAnnotation instanceof DecimalLogicalTypeAnnotation decimalAnnotation + && decimalAnnotation.getPrecision() <= Decimals.MAX_SHORT_PRECISION, + "Column %s is not a short decimal", + descriptor); this.typeLength = descriptor.getPrimitiveType().getTypeLength(); checkArgument(typeLength > 0 && typeLength <= 16, "Expected column %s to have type length in range (1-16)", descriptor); + this.descriptor = descriptor; } @Override @@ -283,7 +288,7 @@ public void read(long[] values, int offset, int length) } for (int i = offset; i < offset + length; i++) { byte[] bytes = delegate.readBytes().getBytes(); - checkBytesFitInShortDecimal(bytes, 0, bytesOffset, decimalType, descriptor); + checkBytesFitInShortDecimal(bytes, 0, bytesOffset, descriptor); values[i] = getShortDecimalValue(bytes, bytesOffset, bytesLength); } } diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/PlainValueDecoders.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/PlainValueDecoders.java index ff8534a0a95f..6276a336e2de 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/PlainValueDecoders.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/PlainValueDecoders.java @@ -16,7 +16,7 @@ import io.airlift.slice.Slices; import io.trino.parquet.reader.SimpleSliceInputStream; import io.trino.parquet.reader.flat.BitPackingUtils; -import io.trino.spi.type.DecimalType; +import io.trino.spi.type.Decimals; import io.trino.spi.type.Int128; import org.apache.parquet.column.ColumnDescriptor; @@ -28,6 +28,7 @@ import static io.trino.parquet.reader.flat.BitPackingUtils.unpack; import static java.lang.Math.min; import static java.util.Objects.requireNonNull; +import static org.apache.parquet.schema.LogicalTypeAnnotation.DecimalLogicalTypeAnnotation; public final class PlainValueDecoders { @@ -242,19 +243,21 @@ public static final class ShortDecimalFixedLengthByteArrayDecoder implements ValueDecoder { private final int typeLength; - private final DecimalType decimalType; private final ColumnDescriptor descriptor; private final ShortDecimalFixedWidthByteArrayBatchDecoder decimalValueDecoder; private SimpleSliceInputStream input; - public ShortDecimalFixedLengthByteArrayDecoder(DecimalType decimalType, ColumnDescriptor descriptor) + public ShortDecimalFixedLengthByteArrayDecoder(ColumnDescriptor descriptor) { - checkArgument(decimalType.isShort(), "Decimal type %s is not a short decimal", decimalType); - this.decimalType = decimalType; - this.descriptor = requireNonNull(descriptor, "descriptor is null"); + DecimalLogicalTypeAnnotation decimalAnnotation = (DecimalLogicalTypeAnnotation) descriptor.getPrimitiveType().getLogicalTypeAnnotation(); + checkArgument( + decimalAnnotation.getPrecision() <= Decimals.MAX_SHORT_PRECISION, + "Decimal type %s is not a short decimal", + decimalAnnotation); this.typeLength = descriptor.getPrimitiveType().getTypeLength(); checkArgument(typeLength > 0 && typeLength <= 16, "Expected column %s to have type length in range (1-16)", descriptor); + this.descriptor = descriptor; this.decimalValueDecoder = new ShortDecimalFixedWidthByteArrayBatchDecoder(Math.min(typeLength, Long.BYTES)); } @@ -276,7 +279,7 @@ public void read(long[] values, int offset, int length) byte[] inputBytes = input.getByteArray(); int inputBytesOffset = input.getByteArrayOffset(); for (int i = offset; i < offset + length; i++) { - checkBytesFitInShortDecimal(inputBytes, inputBytesOffset, extraBytesLength, decimalType, descriptor); + checkBytesFitInShortDecimal(inputBytes, inputBytesOffset, extraBytesLength, descriptor); values[i] = getShortDecimalValue(inputBytes, inputBytesOffset + extraBytesLength, Long.BYTES); inputBytesOffset += typeLength; } diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/TransformingValueDecoders.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/TransformingValueDecoders.java index a91b2d3a7796..f81e9431c619 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/TransformingValueDecoders.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/TransformingValueDecoders.java @@ -13,19 +13,33 @@ */ package io.trino.parquet.reader.decoders; +import io.airlift.slice.Slice; import io.trino.parquet.ParquetEncoding; import io.trino.parquet.PrimitiveField; import io.trino.parquet.reader.SimpleSliceInputStream; +import io.trino.parquet.reader.flat.BinaryBuffer; +import io.trino.spi.type.DecimalConversions; +import io.trino.spi.type.DecimalType; +import io.trino.spi.type.Decimals; +import io.trino.spi.type.Int128; import io.trino.spi.type.TimestampType; import io.trino.spi.type.TimestampWithTimeZoneType; +import org.apache.parquet.io.ParquetDecodingException; import org.joda.time.DateTimeZone; import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.parquet.ParquetReaderUtils.toByteExact; +import static io.trino.parquet.ParquetReaderUtils.toShortExact; +import static io.trino.parquet.ParquetTypeUtils.getShortDecimalValue; +import static io.trino.parquet.reader.decoders.ValueDecoders.getBinaryDecoder; import static io.trino.parquet.reader.decoders.ValueDecoders.getInt96Decoder; +import static io.trino.parquet.reader.decoders.ValueDecoders.getLongDecimalDecoder; import static io.trino.parquet.reader.decoders.ValueDecoders.getLongDecoder; import static io.trino.parquet.reader.decoders.ValueDecoders.getRealDecoder; +import static io.trino.parquet.reader.decoders.ValueDecoders.getShortDecimalDecoder; import static io.trino.parquet.reader.flat.Int96ColumnAdapter.Int96Buffer; import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; +import static io.trino.spi.type.Decimals.longTenToNth; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_SECOND; @@ -37,7 +51,9 @@ import static io.trino.spi.type.Timestamps.round; import static java.lang.Math.floorDiv; import static java.lang.Math.floorMod; +import static java.lang.Math.toIntExact; import static java.util.Objects.requireNonNull; +import static org.apache.parquet.schema.LogicalTypeAnnotation.DecimalLogicalTypeAnnotation; /** * {@link io.trino.parquet.reader.decoders.ValueDecoder} implementations which build on top of implementations from {@link io.trino.parquet.reader.decoders.ValueDecoders}. @@ -421,6 +437,320 @@ public void skip(int n) }; } + public static ValueDecoder getBinaryLongDecimalDecoder(ParquetEncoding encoding, PrimitiveField field) + { + ValueDecoder delegate = getBinaryDecoder(encoding, field); + return new ValueDecoder<>() + { + @Override + public void init(SimpleSliceInputStream input) + { + delegate.init(input); + } + + @Override + public void read(long[] values, int offset, int length) + { + BinaryBuffer buffer = new BinaryBuffer(length); + delegate.read(buffer, 0, length); + int[] offsets = buffer.getOffsets(); + Slice binaryInput = buffer.asSlice(); + + for (int i = 0; i < length; i++) { + int positionOffset = offsets[i]; + int positionLength = offsets[i + 1] - positionOffset; + Int128 value = Int128.fromBigEndian(binaryInput.getBytes(positionOffset, positionLength)); + values[2 * (offset + i)] = value.getHigh(); + values[2 * (offset + i) + 1] = value.getLow(); + } + } + + @Override + public void skip(int n) + { + delegate.skip(n); + } + }; + } + + public static ValueDecoder getBinaryShortDecimalDecoder(ParquetEncoding encoding, PrimitiveField field) + { + ValueDecoder delegate = getBinaryDecoder(encoding, field); + return new ValueDecoder<>() + { + @Override + public void init(SimpleSliceInputStream input) + { + delegate.init(input); + } + + @Override + public void read(long[] values, int offset, int length) + { + BinaryBuffer buffer = new BinaryBuffer(length); + delegate.read(buffer, 0, length); + int[] offsets = buffer.getOffsets(); + byte[] inputBytes = buffer.asSlice().byteArray(); + + for (int i = 0; i < length; i++) { + int positionOffset = offsets[i]; + int positionLength = offsets[i + 1] - positionOffset; + if (positionLength > 8) { + throw new ParquetDecodingException("Unable to read BINARY type decimal of size " + positionLength + " as a short decimal"); + } + // No need for checkBytesFitInShortDecimal as the standard requires variable binary decimals + // to be stored in minimum possible number of bytes + values[offset + i] = getShortDecimalValue(inputBytes, positionOffset, positionLength); + } + } + + @Override + public void skip(int n) + { + delegate.skip(n); + } + }; + } + + public static ValueDecoder getRescaledLongDecimalDecoder(ParquetEncoding encoding, PrimitiveField field) + { + DecimalType decimalType = (DecimalType) field.getType(); + DecimalLogicalTypeAnnotation decimalAnnotation = (DecimalLogicalTypeAnnotation) field.getDescriptor().getPrimitiveType().getLogicalTypeAnnotation(); + if (decimalAnnotation.getPrecision() <= Decimals.MAX_SHORT_PRECISION) { + ValueDecoder delegate = getShortDecimalDecoder(encoding, field); + return new ValueDecoder<>() + { + @Override + public void init(SimpleSliceInputStream input) + { + delegate.init(input); + } + + @Override + public void read(long[] values, int offset, int length) + { + long[] buffer = new long[length]; + delegate.read(buffer, 0, length); + for (int i = 0; i < length; i++) { + Int128 rescaled = DecimalConversions.shortToLongCast( + buffer[i], + decimalAnnotation.getPrecision(), + decimalAnnotation.getScale(), + decimalType.getPrecision(), + decimalType.getScale()); + + values[2 * (offset + i)] = rescaled.getHigh(); + values[2 * (offset + i) + 1] = rescaled.getLow(); + } + } + + @Override + public void skip(int n) + { + delegate.skip(n); + } + }; + } + return new InlineTransformDecoder<>( + getLongDecimalDecoder(encoding, field), + (values, offset, length) -> { + int endOffset = (offset + length) * 2; + for (int currentOffset = offset * 2; currentOffset < endOffset; currentOffset += 2) { + Int128 rescaled = DecimalConversions.longToLongCast( + Int128.valueOf(values[currentOffset], values[currentOffset + 1]), + decimalAnnotation.getPrecision(), + decimalAnnotation.getScale(), + decimalType.getPrecision(), + decimalType.getScale()); + + values[currentOffset] = rescaled.getHigh(); + values[currentOffset + 1] = rescaled.getLow(); + } + }); + } + + public static ValueDecoder getRescaledShortDecimalDecoder(ParquetEncoding encoding, PrimitiveField field) + { + DecimalType decimalType = (DecimalType) field.getType(); + DecimalLogicalTypeAnnotation decimalAnnotation = (DecimalLogicalTypeAnnotation) field.getDescriptor().getPrimitiveType().getLogicalTypeAnnotation(); + if (decimalAnnotation.getPrecision() <= Decimals.MAX_SHORT_PRECISION) { + long rescale = longTenToNth(Math.abs(decimalType.getScale() - decimalAnnotation.getScale())); + return new InlineTransformDecoder<>( + getShortDecimalDecoder(encoding, field), + (values, offset, length) -> { + for (int i = offset; i < offset + length; i++) { + values[i] = DecimalConversions.shortToShortCast( + values[i], + decimalAnnotation.getPrecision(), + decimalAnnotation.getScale(), + decimalType.getPrecision(), + decimalType.getScale(), + rescale, + rescale / 2); + } + }); + } + ValueDecoder delegate = getLongDecimalDecoder(encoding, field); + return new ValueDecoder<>() + { + @Override + public void init(SimpleSliceInputStream input) + { + delegate.init(input); + } + + @Override + public void read(long[] values, int offset, int length) + { + long[] buffer = new long[2 * length]; + delegate.read(buffer, 0, length); + for (int i = 0; i < length; i++) { + values[offset + i] = DecimalConversions.longToShortCast( + Int128.valueOf(buffer[2 * i], buffer[2 * i + 1]), + decimalAnnotation.getPrecision(), + decimalAnnotation.getScale(), + decimalType.getPrecision(), + decimalType.getScale()); + } + } + + @Override + public void skip(int n) + { + delegate.skip(n); + } + }; + } + + public static ValueDecoder getInt64ToIntDecoder(ParquetEncoding encoding, PrimitiveField field) + { + return new LongToIntTransformDecoder(getLongDecoder(encoding, field)); + } + + public static ValueDecoder getShortDecimalToIntDecoder(ParquetEncoding encoding, PrimitiveField field) + { + return new LongToIntTransformDecoder(getShortDecimalDecoder(encoding, field)); + } + + public static ValueDecoder getInt64ToShortDecoder(ParquetEncoding encoding, PrimitiveField field) + { + return new LongToShortTransformDecoder(getLongDecoder(encoding, field)); + } + + public static ValueDecoder getShortDecimalToShortDecoder(ParquetEncoding encoding, PrimitiveField field) + { + return new LongToShortTransformDecoder(getShortDecimalDecoder(encoding, field)); + } + + public static ValueDecoder getInt64ToByteDecoder(ParquetEncoding encoding, PrimitiveField field) + { + return new LongToByteTransformDecoder(getLongDecoder(encoding, field)); + } + + public static ValueDecoder getShortDecimalToByteDecoder(ParquetEncoding encoding, PrimitiveField field) + { + return new LongToByteTransformDecoder(getShortDecimalDecoder(encoding, field)); + } + + private static class LongToIntTransformDecoder + implements ValueDecoder + { + private final ValueDecoder delegate; + + private LongToIntTransformDecoder(ValueDecoder delegate) + { + this.delegate = delegate; + } + + @Override + public void init(SimpleSliceInputStream input) + { + delegate.init(input); + } + + @Override + public void read(int[] values, int offset, int length) + { + long[] buffer = new long[length]; + delegate.read(buffer, 0, length); + for (int i = 0; i < length; i++) { + values[offset + i] = toIntExact(buffer[i]); + } + } + + @Override + public void skip(int n) + { + delegate.skip(n); + } + } + + private static class LongToShortTransformDecoder + implements ValueDecoder + { + private final ValueDecoder delegate; + + private LongToShortTransformDecoder(ValueDecoder delegate) + { + this.delegate = delegate; + } + + @Override + public void init(SimpleSliceInputStream input) + { + delegate.init(input); + } + + @Override + public void read(short[] values, int offset, int length) + { + long[] buffer = new long[length]; + delegate.read(buffer, 0, length); + for (int i = 0; i < length; i++) { + values[offset + i] = toShortExact(buffer[i]); + } + } + + @Override + public void skip(int n) + { + delegate.skip(n); + } + } + + private static class LongToByteTransformDecoder + implements ValueDecoder + { + private final ValueDecoder delegate; + + private LongToByteTransformDecoder(ValueDecoder delegate) + { + this.delegate = delegate; + } + + @Override + public void init(SimpleSliceInputStream input) + { + delegate.init(input); + } + + @Override + public void read(byte[] values, int offset, int length) + { + long[] buffer = new long[length]; + delegate.read(buffer, 0, length); + for (int i = 0; i < length; i++) { + values[offset + i] = toByteExact(buffer[i]); + } + } + + @Override + public void skip(int n) + { + delegate.skip(n); + } + } + private static class InlineTransformDecoder implements ValueDecoder { diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ValueDecoders.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ValueDecoders.java index 89e2e9a7101e..96cd3321ac6f 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ValueDecoders.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ValueDecoders.java @@ -21,10 +21,10 @@ import io.trino.parquet.reader.flat.ColumnAdapter; import io.trino.parquet.reader.flat.DictionaryDecoder; import io.trino.spi.type.CharType; -import io.trino.spi.type.DecimalType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; import org.apache.parquet.column.values.ValuesReader; +import org.apache.parquet.schema.PrimitiveType; import static com.google.common.base.Preconditions.checkArgument; import static io.trino.parquet.ParquetEncoding.PLAIN; @@ -54,7 +54,13 @@ import static io.trino.parquet.reader.decoders.PlainValueDecoders.LongPlainValueDecoder; import static io.trino.parquet.reader.decoders.PlainValueDecoders.ShortDecimalFixedLengthByteArrayDecoder; import static io.trino.parquet.reader.decoders.PlainValueDecoders.UuidPlainValueDecoder; +import static io.trino.parquet.reader.decoders.TransformingValueDecoders.getBinaryLongDecimalDecoder; +import static io.trino.parquet.reader.decoders.TransformingValueDecoders.getBinaryShortDecimalDecoder; +import static io.trino.parquet.reader.decoders.TransformingValueDecoders.getInt64ToByteDecoder; +import static io.trino.parquet.reader.decoders.TransformingValueDecoders.getInt64ToIntDecoder; +import static io.trino.parquet.reader.decoders.TransformingValueDecoders.getInt64ToShortDecoder; import static io.trino.parquet.reader.flat.Int96ColumnAdapter.Int96Buffer; +import static org.apache.parquet.schema.LogicalTypeAnnotation.DecimalLogicalTypeAnnotation; /** * This class provides static API for creating value decoders for given fields and encodings. @@ -85,11 +91,16 @@ public static ValueDecoder getRealDecoder(ParquetEncoding encoding, Primi public static ValueDecoder getShortDecimalDecoder(ParquetEncoding encoding, PrimitiveField field) { - checkArgument(field.getType() instanceof DecimalType, "Trino type %s is not a decimal", field.getType()); - return switch (field.getDescriptor().getPrimitiveType().getPrimitiveTypeName()) { + PrimitiveType primitiveType = field.getDescriptor().getPrimitiveType(); + checkArgument( + primitiveType.getLogicalTypeAnnotation() instanceof DecimalLogicalTypeAnnotation, + "Column %s is not annotated as a decimal", + field); + return switch (primitiveType.getPrimitiveTypeName()) { case INT64 -> getLongDecoder(encoding, field); case INT32 -> getIntToLongDecoder(encoding, field); - case FIXED_LEN_BYTE_ARRAY -> getFixedWidthShortDecimalDecoder(encoding, field, (DecimalType) field.getType()); + case FIXED_LEN_BYTE_ARRAY -> getFixedWidthShortDecimalDecoder(encoding, field); + case BINARY -> getBinaryShortDecimalDecoder(encoding, field); default -> throw wrongEncoding(encoding, field); }; } @@ -117,8 +128,7 @@ public static ValueDecoder getLongDecoder(ParquetEncoding encoding, Prim { return switch (encoding) { case PLAIN -> new LongPlainValueDecoder(); - case DELTA_BINARY_PACKED, RLE, BIT_PACKED -> - new LongApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); + case DELTA_BINARY_PACKED -> new LongApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); default -> throw wrongEncoding(encoding, field); }; } @@ -128,38 +138,34 @@ public static ValueDecoder getIntToLongDecoder(ParquetEncoding encoding, // We need to produce LongArrayBlock from the decoded integers for INT32 backed decimals and bigints return switch (encoding) { case PLAIN -> new IntToLongPlainValueDecoder(); - case DELTA_BINARY_PACKED, RLE, BIT_PACKED -> - new IntToLongApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); + case DELTA_BINARY_PACKED -> new IntToLongApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); default -> throw wrongEncoding(encoding, field); }; } public static ValueDecoder getIntDecoder(ParquetEncoding encoding, PrimitiveField field) { - return switch (encoding) { - case PLAIN -> new IntPlainValueDecoder(); - case DELTA_BINARY_PACKED, RLE, BIT_PACKED -> - new IntApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); + return switch (field.getDescriptor().getPrimitiveType().getPrimitiveTypeName()) { + case INT64 -> getInt64ToIntDecoder(encoding, field); + case INT32 -> getInt32Decoder(encoding, field); default -> throw wrongEncoding(encoding, field); }; } - public static ValueDecoder getByteDecoder(ParquetEncoding encoding, PrimitiveField field) + public static ValueDecoder getShortDecoder(ParquetEncoding encoding, PrimitiveField field) { - return switch (encoding) { - case PLAIN -> new IntToBytePlainValueDecoder(); - case DELTA_BINARY_PACKED, RLE, BIT_PACKED -> - new ByteApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); + return switch (field.getDescriptor().getPrimitiveType().getPrimitiveTypeName()) { + case INT64 -> getInt64ToShortDecoder(encoding, field); + case INT32 -> getInt32ToShortDecoder(encoding, field); default -> throw wrongEncoding(encoding, field); }; } - public static ValueDecoder getShortDecoder(ParquetEncoding encoding, PrimitiveField field) + public static ValueDecoder getByteDecoder(ParquetEncoding encoding, PrimitiveField field) { - return switch (encoding) { - case PLAIN -> new IntToShortPlainValueDecoder(); - case DELTA_BINARY_PACKED, RLE, BIT_PACKED -> - new ShortApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); + return switch (field.getDescriptor().getPrimitiveType().getPrimitiveTypeName()) { + case INT64 -> getInt64ToByteDecoder(encoding, field); + case INT32 -> getInt32ToByteDecoder(encoding, field); default -> throw wrongEncoding(encoding, field); }; } @@ -181,13 +187,12 @@ public static ValueDecoder getInt96Decoder(ParquetEncoding encoding throw wrongEncoding(encoding, field); } - public static ValueDecoder getFixedWidthShortDecimalDecoder(ParquetEncoding encoding, PrimitiveField field, DecimalType decimalType) + public static ValueDecoder getFixedWidthShortDecimalDecoder(ParquetEncoding encoding, PrimitiveField field) { return switch (encoding) { - case PLAIN -> new ShortDecimalFixedLengthByteArrayDecoder(decimalType, field.getDescriptor()); + case PLAIN -> new ShortDecimalFixedLengthByteArrayDecoder(field.getDescriptor()); case DELTA_BYTE_ARRAY -> new ShortDecimalApacheParquetValueDecoder( getApacheParquetReader(encoding, field), - decimalType, field.getDescriptor()); default -> throw wrongEncoding(encoding, field); }; @@ -203,15 +208,6 @@ public static ValueDecoder getFixedWidthLongDecimalDecoder(ParquetEncodi }; } - private static ValueDecoder getBinaryLongDecimalDecoder(ParquetEncoding encoding, PrimitiveField field) - { - return switch (encoding) { - case PLAIN, DELTA_LENGTH_BYTE_ARRAY, DELTA_BYTE_ARRAY -> - new LongDecimalApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); - default -> throw wrongEncoding(encoding, field); - }; - } - public static ValueDecoder getBoundedVarcharBinaryDecoder(ParquetEncoding encoding, PrimitiveField field) { Type trinoType = field.getType(); @@ -267,6 +263,33 @@ public static DictionaryDecoder getDictionaryDecoder( return new DictionaryDecoder<>(dictionary, columnAdapter, size, isNonNull); } + private static ValueDecoder getInt32Decoder(ParquetEncoding encoding, PrimitiveField field) + { + return switch (encoding) { + case PLAIN -> new IntPlainValueDecoder(); + case DELTA_BINARY_PACKED -> new IntApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); + default -> throw wrongEncoding(encoding, field); + }; + } + + private static ValueDecoder getInt32ToShortDecoder(ParquetEncoding encoding, PrimitiveField field) + { + return switch (encoding) { + case PLAIN -> new IntToShortPlainValueDecoder(); + case DELTA_BINARY_PACKED -> new ShortApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); + default -> throw wrongEncoding(encoding, field); + }; + } + + private static ValueDecoder getInt32ToByteDecoder(ParquetEncoding encoding, PrimitiveField field) + { + return switch (encoding) { + case PLAIN -> new IntToBytePlainValueDecoder(); + case DELTA_BINARY_PACKED -> new ByteApacheParquetValueDecoder(getApacheParquetReader(encoding, field)); + default -> throw wrongEncoding(encoding, field); + }; + } + private static ValuesReader getApacheParquetReader(ParquetEncoding encoding, PrimitiveField field) { return encoding.getValuesReader(field.getDescriptor(), VALUES); @@ -274,6 +297,6 @@ private static ValuesReader getApacheParquetReader(ParquetEncoding encoding, Pri private static IllegalArgumentException wrongEncoding(ParquetEncoding encoding, PrimitiveField field) { - return new IllegalArgumentException("Wrong encoding " + encoding + " for column type " + field.getDescriptor().getPrimitiveType().getPrimitiveTypeName()); + return new IllegalArgumentException("Wrong encoding " + encoding + " for column " + field.getDescriptor()); } } diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/writer/ParquetSchemaConverter.java b/lib/trino-parquet/src/main/java/io/trino/parquet/writer/ParquetSchemaConverter.java index 10f245195940..7100e28ef97d 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/writer/ParquetSchemaConverter.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/writer/ParquetSchemaConverter.java @@ -14,6 +14,7 @@ package io.trino.parquet.writer; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import io.trino.spi.TrinoException; import io.trino.spi.type.ArrayType; import io.trino.spi.type.CharType; @@ -32,9 +33,9 @@ import org.apache.parquet.schema.Type.Repetition; import org.apache.parquet.schema.Types; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import static com.google.common.base.Preconditions.checkArgument; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; @@ -71,9 +72,7 @@ public class ParquetSchemaConverter public static final boolean HIVE_PARQUET_USE_LEGACY_DECIMAL_ENCODING = true; public static final boolean HIVE_PARQUET_USE_INT96_TIMESTAMP_ENCODING = true; - private Map, Type> primitiveTypes = new HashMap<>(); - private final boolean useLegacyDecimalEncoding; - private final boolean useInt96TimestampEncoding; + private final Map, Type> primitiveTypes; private final MessageType messageType; public ParquetSchemaConverter(List types, List columnNames, boolean useLegacyDecimalEncoding, boolean useInt96TimestampEncoding) @@ -81,46 +80,74 @@ public ParquetSchemaConverter(List types, List columnNames, boolea requireNonNull(types, "types is null"); requireNonNull(columnNames, "columnNames is null"); checkArgument(types.size() == columnNames.size(), "types size not equals to columnNames size"); - this.useLegacyDecimalEncoding = useLegacyDecimalEncoding; - this.useInt96TimestampEncoding = useInt96TimestampEncoding; - this.messageType = convert(types, columnNames); + ImmutableMap.Builder, Type> primitiveTypesBuilder = ImmutableMap.builder(); + messageType = convert(types, columnNames, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesBuilder::put); + primitiveTypes = primitiveTypesBuilder.buildOrThrow(); } - private MessageType convert(List types, List columnNames) + public Map, Type> getPrimitiveTypes() + { + return primitiveTypes; + } + + public MessageType getMessageType() + { + return messageType; + } + + private static MessageType convert( + List types, + List columnNames, + boolean useLegacyDecimalEncoding, + boolean useInt96TimestampEncoding, + BiConsumer, Type> primitiveTypesConsumer) { Types.MessageTypeBuilder builder = Types.buildMessage(); for (int i = 0; i < types.size(); i++) { - builder.addField(convert(types.get(i), columnNames.get(i), ImmutableList.of(), OPTIONAL)); + builder.addField(convert(types.get(i), columnNames.get(i), ImmutableList.of(), OPTIONAL, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesConsumer)); } return builder.named("trino_schema"); } - private org.apache.parquet.schema.Type convert(Type type, String name, List parent, Repetition repetition) + private static org.apache.parquet.schema.Type convert( + Type type, + String name, + List parent, + Repetition repetition, + boolean useLegacyDecimalEncoding, + boolean useInt96TimestampEncoding, + BiConsumer, Type> primitiveTypesConsumer) { if (ROW.equals(type.getTypeSignature().getBase())) { - return getRowType((RowType) type, name, parent, repetition); + return getRowType((RowType) type, name, parent, repetition, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesConsumer); } if (MAP.equals(type.getTypeSignature().getBase())) { - return getMapType((MapType) type, name, parent, repetition); + return getMapType((MapType) type, name, parent, repetition, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesConsumer); } if (ARRAY.equals(type.getTypeSignature().getBase())) { - return getArrayType((ArrayType) type, name, parent, repetition); + return getArrayType((ArrayType) type, name, parent, repetition, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesConsumer); } - return getPrimitiveType(type, name, parent, repetition); + return getPrimitiveType(type, name, parent, repetition, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesConsumer); } - private org.apache.parquet.schema.Type getPrimitiveType(Type type, String name, List parent, Repetition repetition) + private static org.apache.parquet.schema.Type getPrimitiveType( + Type type, + String name, + List parent, + Repetition repetition, + boolean useLegacyDecimalEncoding, + boolean useInt96TimestampEncoding, + BiConsumer, Type> primitiveTypesConsumer) { List fullName = ImmutableList.builder().addAll(parent).add(name).build(); - primitiveTypes.put(fullName, type); + primitiveTypesConsumer.accept(fullName, type); if (BOOLEAN.equals(type)) { return Types.primitive(PrimitiveType.PrimitiveTypeName.BOOLEAN, repetition).named(name); } if (INTEGER.equals(type) || SMALLINT.equals(type) || TINYINT.equals(type)) { return Types.primitive(PrimitiveType.PrimitiveTypeName.INT32, repetition).named(name); } - if (type instanceof DecimalType) { - DecimalType decimalType = (DecimalType) type; + if (type instanceof DecimalType decimalType) { // Apache Hive version 3 or lower does not support reading decimals encoded as INT32/INT64 if (!useLegacyDecimalEncoding) { if (decimalType.getPrecision() <= 9) { @@ -146,8 +173,7 @@ private org.apache.parquet.schema.Type getPrimitiveType(Type type, String name, return Types.primitive(PrimitiveType.PrimitiveTypeName.INT64, repetition).named(name); } - if (type instanceof TimestampType) { - TimestampType timestampType = (TimestampType) type; + if (type instanceof TimestampType timestampType) { // Apache Hive version 3.x or lower does not support reading timestamps encoded as INT64 if (useInt96TimestampEncoding) { return Types.primitive(PrimitiveType.PrimitiveTypeName.INT96, repetition).named(name); @@ -180,43 +206,54 @@ private org.apache.parquet.schema.Type getPrimitiveType(Type type, String name, throw new TrinoException(NOT_SUPPORTED, format("Unsupported primitive type: %s", type)); } - private org.apache.parquet.schema.Type getArrayType(ArrayType type, String name, List parent, Repetition repetition) + private static org.apache.parquet.schema.Type getArrayType( + ArrayType type, + String name, + List parent, + Repetition repetition, + boolean useLegacyDecimalEncoding, + boolean useInt96TimestampEncoding, + BiConsumer, Type> primitiveTypesConsumer) { Type elementType = type.getElementType(); return Types.list(repetition) - .element(convert(elementType, "element", ImmutableList.builder().addAll(parent).add(name).add("list").build(), OPTIONAL)) + .element(convert(elementType, "element", ImmutableList.builder().addAll(parent).add(name).add("list").build(), OPTIONAL, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesConsumer)) .named(name); } - private org.apache.parquet.schema.Type getMapType(MapType type, String name, List parent, Repetition repetition) + private static org.apache.parquet.schema.Type getMapType( + MapType type, + String name, + List parent, + Repetition repetition, + boolean useLegacyDecimalEncoding, + boolean useInt96TimestampEncoding, + BiConsumer, Type> primitiveTypesConsumer) { parent = ImmutableList.builder().addAll(parent).add(name).add("key_value").build(); Type keyType = type.getKeyType(); Type valueType = type.getValueType(); return Types.map(repetition) - .key(convert(keyType, "key", parent, REQUIRED)) - .value(convert(valueType, "value", parent, OPTIONAL)) + .key(convert(keyType, "key", parent, REQUIRED, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesConsumer)) + .value(convert(valueType, "value", parent, OPTIONAL, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesConsumer)) .named(name); } - private org.apache.parquet.schema.Type getRowType(RowType type, String name, List parent, Repetition repetition) + private static org.apache.parquet.schema.Type getRowType( + RowType type, + String name, + List parent, + Repetition repetition, + boolean useLegacyDecimalEncoding, + boolean useInt96TimestampEncoding, + BiConsumer, Type> primitiveTypesConsumer) { parent = ImmutableList.builder().addAll(parent).add(name).build(); Types.GroupBuilder builder = Types.buildGroup(repetition); for (RowType.Field field : type.getFields()) { checkArgument(field.getName().isPresent(), "field in struct type doesn't have name"); - builder.addField(convert(field.getType(), field.getName().get(), parent, OPTIONAL)); + builder.addField(convert(field.getType(), field.getName().get(), parent, OPTIONAL, useLegacyDecimalEncoding, useInt96TimestampEncoding, primitiveTypesConsumer)); } return builder.named(name); } - - public Map, Type> getPrimitiveTypes() - { - return primitiveTypes; - } - - public MessageType getMessageType() - { - return messageType; - } } diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestingColumnReader.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestingColumnReader.java index ddd4a891ee55..3788567c2b0c 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestingColumnReader.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestingColumnReader.java @@ -643,6 +643,9 @@ private static ColumnReaderFormat[] columnReaders() new ColumnReaderFormat<>(BINARY, VARCHAR, PLAIN_WRITER, DICTIONARY_BINARY_WRITER, WRITE_BINARY, ASSERT_BINARY), new ColumnReaderFormat<>(INT64, decimalType(0, 16), createDecimalType(16), PLAIN_WRITER, DICTIONARY_LONG_WRITER, WRITE_LONG, ASSERT_LONG), new ColumnReaderFormat<>(INT64, BIGINT, PLAIN_WRITER, DICTIONARY_LONG_WRITER, WRITE_LONG, ASSERT_LONG), + new ColumnReaderFormat<>(INT64, INTEGER, PLAIN_WRITER, DICTIONARY_LONG_WRITER, WRITE_LONG, ASSERT_INT), + new ColumnReaderFormat<>(INT64, SMALLINT, PLAIN_WRITER, DICTIONARY_LONG_WRITER, WRITE_LONG, ASSERT_SHORT), + new ColumnReaderFormat<>(INT64, TINYINT, PLAIN_WRITER, DICTIONARY_LONG_WRITER, WRITE_LONG, ASSERT_BYTE), new ColumnReaderFormat<>(FIXED_LEN_BYTE_ARRAY, 8, decimalType(0, 2), createDecimalType(2), FIXED_LENGTH_WRITER, DICTIONARY_FIXED_LENGTH_WRITER, WRITE_SHORT_DECIMAL, ASSERT_LONG), new ColumnReaderFormat<>(FIXED_LEN_BYTE_ARRAY, 16, decimalType(2, 38), createDecimalType(38, 2), FIXED_LENGTH_WRITER, DICTIONARY_FIXED_LENGTH_WRITER, writeLongDecimal(16), ASSERT_INT_128), new ColumnReaderFormat<>(FIXED_LEN_BYTE_ARRAY, 16, uuidType(), UUID, FIXED_LENGTH_WRITER, DICTIONARY_FIXED_LENGTH_WRITER, WRITE_UUID, ASSERT_INT_128), @@ -651,7 +654,8 @@ private static ColumnReaderFormat[] columnReaders() new ColumnReaderFormat<>(INT64, timeType(false, MICROS), TimeType.TIME_MICROS, PLAIN_WRITER, DICTIONARY_LONG_WRITER, WRITE_LONG, assertTime(6)), // Short decimals new ColumnReaderFormat<>(INT32, decimalType(0, 8), createDecimalType(8), PLAIN_WRITER, DICTIONARY_INT_WRITER, WRITE_INT, ASSERT_INT), - new ColumnReaderFormat<>(INT32, createDecimalType(9), PLAIN_WRITER, DICTIONARY_INT_WRITER, WRITE_INT, ASSERT_INT), + // INT32 values can be read as zero scale decimals provided the precision is at least 10 to accommodate the largest possible integer + new ColumnReaderFormat<>(INT32, createDecimalType(10), PLAIN_WRITER, DICTIONARY_INT_WRITER, WRITE_INT, ASSERT_INT), new ColumnReaderFormat<>(INT32, decimalType(0, 8), BIGINT, PLAIN_WRITER, DICTIONARY_INT_WRITER, WRITE_INT, ASSERT_LONG), new ColumnReaderFormat<>(INT32, decimalType(0, 8), INTEGER, PLAIN_WRITER, DICTIONARY_INT_WRITER, WRITE_INT, ASSERT_INT), new ColumnReaderFormat<>(INT32, decimalType(0, 8), SMALLINT, PLAIN_WRITER, DICTIONARY_INT_WRITER, WRITE_SHORT, ASSERT_SHORT), diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/AbstractValueDecodersTest.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/AbstractValueDecodersTest.java index 596c4d8061ab..776623d57f82 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/AbstractValueDecodersTest.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/AbstractValueDecodersTest.java @@ -22,6 +22,7 @@ import io.trino.parquet.reader.SimpleSliceInputStream; import io.trino.parquet.reader.TestingColumnReader; import io.trino.parquet.reader.flat.ColumnAdapter; +import io.trino.spi.type.DecimalType; import io.trino.spi.type.Type; import org.apache.parquet.bytes.ByteBufferInputStream; import org.apache.parquet.bytes.HeapByteBufferAllocator; @@ -33,6 +34,7 @@ import org.apache.parquet.column.values.plain.BooleanPlainValuesWriter; import org.apache.parquet.column.values.plain.FixedLenByteArrayPlainValuesWriter; import org.apache.parquet.column.values.plain.PlainValuesWriter; +import org.apache.parquet.schema.LogicalTypeAnnotation; import org.apache.parquet.schema.PrimitiveType; import org.apache.parquet.schema.Types; import org.testng.annotations.DataProvider; @@ -252,6 +254,9 @@ static PrimitiveField createField(PrimitiveTypeName typeName, OptionalInt typeLe if (typeLength.isPresent()) { builder = builder.length(typeLength.getAsInt()); } + if (trinoType instanceof DecimalType decimalType) { + builder = builder.as(LogicalTypeAnnotation.decimalType(decimalType.getScale(), decimalType.getPrecision())); + } return new PrimitiveField( trinoType, true, diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/TestFixedWidthByteArrayValueDecoders.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/TestFixedWidthByteArrayValueDecoders.java index 77bf02e725c8..13ee45972ce4 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/TestFixedWidthByteArrayValueDecoders.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/TestFixedWidthByteArrayValueDecoders.java @@ -82,8 +82,8 @@ private static TestType createShortDecimalTestType(int typeLength, int p PrimitiveField primitiveField = createField(FIXED_LEN_BYTE_ARRAY, OptionalInt.of(typeLength), decimalType); return new TestType<>( primitiveField, - (encoding, field) -> getFixedWidthShortDecimalDecoder(encoding, field, decimalType), - valuesReader -> new ShortDecimalApacheParquetValueDecoder(valuesReader, decimalType, primitiveField.getDescriptor()), + ValueDecoders::getFixedWidthShortDecimalDecoder, + valuesReader -> new ShortDecimalApacheParquetValueDecoder(valuesReader, primitiveField.getDescriptor()), LONG_ADAPTER, (actual, expected) -> assertThat(actual).isEqualTo(expected)); } diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestFlatColumnReader.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestFlatColumnReader.java index 8a913cb6f140..38bc15bc30a5 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestFlatColumnReader.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestFlatColumnReader.java @@ -43,7 +43,6 @@ import org.apache.parquet.schema.PrimitiveType; import org.apache.parquet.schema.Types; import org.apache.parquet.schema.Types.PrimitiveBuilder; -import org.testng.SkipException; import org.testng.annotations.Test; import javax.annotation.Nullable; @@ -155,7 +154,7 @@ public void testSingleValueDictionary(DataPageVersion version, ColumnReaderF reader.prepareNextRead(2); Block actual = reader.readPrimitive().getBlock(); assertThat(actual.mayHaveNull()).isFalse(); - if (reader instanceof FlatColumnReader && field.getType() instanceof AbstractVariableWidthType) { + if (field.getType() instanceof AbstractVariableWidthType) { assertThat(actual).isInstanceOf(DictionaryBlock.class); } format.assertBlock(values, actual); @@ -178,7 +177,7 @@ public void testSingleValueDictionaryNullable(DataPageVersion version, Colum reader.prepareNextRead(2); Block actual = reader.readPrimitive().getBlock(); assertThat(actual.mayHaveNull()).isTrue(); - if (reader instanceof FlatColumnReader && field.getType() instanceof AbstractVariableWidthType) { + if (field.getType() instanceof AbstractVariableWidthType) { assertThat(actual).isInstanceOf(DictionaryBlock.class); } format.assertBlock(values, actual); @@ -200,7 +199,7 @@ public void testSingleValueDictionaryNullableWithNoNulls(DataPageVersion ver reader.setPageReader(getPageReaderMock(List.of(page), dictionaryPage), Optional.empty()); reader.prepareNextRead(2); Block actual = reader.readPrimitive().getBlock(); - if (reader instanceof FlatColumnReader && field.getType() instanceof AbstractVariableWidthType) { + if (field.getType() instanceof AbstractVariableWidthType) { assertThat(actual).isInstanceOf(DictionaryBlock.class); assertThat(actual.mayHaveNull()).isTrue(); } @@ -223,7 +222,7 @@ public void testSingleValueDictionaryNullableWithNoNullsUsingColumnStats(Dat reader.setPageReader(getPageReaderMock(List.of(page), dictionaryPage, true), Optional.empty()); reader.prepareNextRead(2); Block actual = reader.readPrimitive().getBlock(); - if (reader instanceof FlatColumnReader && field.getType() instanceof AbstractVariableWidthType) { + if (field.getType() instanceof AbstractVariableWidthType) { assertThat(actual).isInstanceOf(DictionaryBlock.class); assertThat(actual.mayHaveNull()).isFalse(); } @@ -273,7 +272,7 @@ public void testDictionariesSharedBetweenPages(DataPageVersion version, Colu reader.prepareNextRead(2); Block block2 = reader.readPrimitive().getBlock(); - if (reader instanceof FlatColumnReader && field.getType() instanceof AbstractVariableWidthType) { + if (field.getType() instanceof AbstractVariableWidthType) { assertThat(block1).isInstanceOf(DictionaryBlock.class); assertThat(block2).isInstanceOf(DictionaryBlock.class); @@ -421,7 +420,7 @@ public void testReadNullableDictionary(DataPageVersion version, ColumnReader Block actual2 = readBlock(reader, 3); Block actual3 = readBlock(reader, 4); - if (reader instanceof FlatColumnReader && field.getType() instanceof AbstractVariableWidthType) { + if (field.getType() instanceof AbstractVariableWidthType) { assertThat(actual1).isInstanceOf(DictionaryBlock.class); assertThat(actual2).isInstanceOf(DictionaryBlock.class); assertThat(actual3).isInstanceOf(DictionaryBlock.class); @@ -601,9 +600,6 @@ public void testMemoryUsage(DataPageVersion version, ColumnReaderFormat f PrimitiveField field = createField(format, true); AggregatedMemoryContext memoryContext = newSimpleAggregatedMemoryContext(); ColumnReader reader = ColumnReaderFactory.create(field, UTC, memoryContext, true); - if (!(reader instanceof FlatColumnReader)) { - throw new SkipException("Memory usage is tracked only in batched column readers"); - } // Write data DictionaryValuesWriter dictionaryWriter = format.getDictionaryWriter(); format.write(dictionaryWriter, new Integer[] {1, 2, 3}); diff --git a/lib/trino-phoenix5-patched/pom.xml b/lib/trino-phoenix5-patched/pom.xml index 7c74c7ad2eba..3631b2dbe6c6 100644 --- a/lib/trino-phoenix5-patched/pom.xml +++ b/lib/trino-phoenix5-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index fe4120b33b5b..b5986b859803 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-rcfile/pom.xml b/lib/trino-rcfile/pom.xml index 61ec319101eb..377a4a845c16 100644 --- a/lib/trino-rcfile/pom.xml +++ b/lib/trino-rcfile/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index 8ad00971067a..1a97b3349c7a 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/AbstractDateTimeJsonValueProvider.java b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/AbstractDateTimeJsonValueProvider.java index d016db84e375..b63bc4286f28 100644 --- a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/AbstractDateTimeJsonValueProvider.java +++ b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/AbstractDateTimeJsonValueProvider.java @@ -28,7 +28,7 @@ import static io.trino.spi.type.DateTimeEncoding.packTimeWithTimeZone; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; @@ -62,7 +62,7 @@ public final long getLong() Type type = columnHandle.getType(); - if (type.equals(TIME_MILLIS) || type.equals(TIME_WITH_TIME_ZONE)) { + if (type.equals(TIME_MILLIS) || type.equals(TIME_TZ_MILLIS)) { if (millis < 0 || millis >= TimeUnit.DAYS.toMillis(1)) { throw new TrinoException( DECODER_CONVERSION_NOT_SUPPORTED, @@ -82,7 +82,7 @@ public final long getLong() if (type.equals(TIMESTAMP_TZ_MILLIS)) { return packDateTimeWithZone(millis, getTimeZone()); } - if (type.equals(TIME_WITH_TIME_ZONE)) { + if (type.equals(TIME_TZ_MILLIS)) { int offsetMinutes = getTimeZone().getZoneId().getRules().getOffset(Instant.ofEpochMilli(millis)).getTotalSeconds() / 60; return packTimeWithTimeZone((millis + (offsetMinutes * 60 * MILLISECONDS_PER_SECOND)) * NANOSECONDS_PER_MILLISECOND, offsetMinutes); } diff --git a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/CustomDateTimeJsonFieldDecoder.java b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/CustomDateTimeJsonFieldDecoder.java index d9158c1e6e3f..e2ee5c2fded2 100644 --- a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/CustomDateTimeJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/CustomDateTimeJsonFieldDecoder.java @@ -33,7 +33,7 @@ import static io.trino.spi.StandardErrorCode.GENERIC_USER_ERROR; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; @@ -50,7 +50,7 @@ public class CustomDateTimeJsonFieldDecoder implements JsonFieldDecoder { - private static final Set SUPPORTED_TYPES = ImmutableSet.of(DATE, TIME_MILLIS, TIME_WITH_TIME_ZONE, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS); + private static final Set SUPPORTED_TYPES = ImmutableSet.of(DATE, TIME_MILLIS, TIME_TZ_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS); private final DecoderColumnHandle columnHandle; private final DateTimeFormatter formatter; diff --git a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/ISO8601JsonFieldDecoder.java b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/ISO8601JsonFieldDecoder.java index d27bbaa7390e..9054fd75115b 100644 --- a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/ISO8601JsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/ISO8601JsonFieldDecoder.java @@ -33,7 +33,7 @@ import static io.trino.spi.type.DateTimeEncoding.packTimeWithTimeZone; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; @@ -60,7 +60,7 @@ public class ISO8601JsonFieldDecoder implements JsonFieldDecoder { - private static final Set SUPPORTED_TYPES = ImmutableSet.of(DATE, TIME_MILLIS, TIME_WITH_TIME_ZONE, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS); + private static final Set SUPPORTED_TYPES = ImmutableSet.of(DATE, TIME_MILLIS, TIME_TZ_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS); private final DecoderColumnHandle columnHandle; @@ -129,7 +129,7 @@ public long getLong() if (columnType.equals(TIME_MILLIS)) { return ISO_TIME.parse(textValue).getLong(MILLI_OF_DAY) * PICOSECONDS_PER_MILLISECOND; } - if (columnType.equals(TIME_WITH_TIME_ZONE)) { + if (columnType.equals(TIME_TZ_MILLIS)) { TemporalAccessor parseResult = ISO_OFFSET_TIME.parse(textValue); return packTimeWithTimeZone((long) (parseResult.get(MILLI_OF_DAY)) * NANOSECONDS_PER_MILLISECOND, ZoneOffset.from(parseResult).getTotalSeconds() / 60); } diff --git a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/MillisecondsSinceEpochJsonFieldDecoder.java b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/MillisecondsSinceEpochJsonFieldDecoder.java index 33ac8249f842..f30db8d441cb 100644 --- a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/MillisecondsSinceEpochJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/MillisecondsSinceEpochJsonFieldDecoder.java @@ -26,7 +26,7 @@ import static io.trino.decoder.DecoderErrorCode.DECODER_CONVERSION_NOT_SUPPORTED; import static io.trino.decoder.json.JsonRowDecoderFactory.throwUnsupportedColumnType; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static java.lang.Long.parseLong; @@ -41,7 +41,7 @@ public class MillisecondsSinceEpochJsonFieldDecoder implements JsonFieldDecoder { - private static final Set SUPPORTED_TYPES = ImmutableSet.of(TIME_MILLIS, TIME_WITH_TIME_ZONE, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS); + private static final Set SUPPORTED_TYPES = ImmutableSet.of(TIME_MILLIS, TIME_TZ_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS); private final DecoderColumnHandle columnHandle; diff --git a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/SecondsSinceEpochJsonFieldDecoder.java b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/SecondsSinceEpochJsonFieldDecoder.java index 1f39f1c1a558..31e29e0ab795 100644 --- a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/SecondsSinceEpochJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/SecondsSinceEpochJsonFieldDecoder.java @@ -26,7 +26,7 @@ import static io.trino.decoder.DecoderErrorCode.DECODER_CONVERSION_NOT_SUPPORTED; import static io.trino.decoder.json.JsonRowDecoderFactory.throwUnsupportedColumnType; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; @@ -43,7 +43,7 @@ public class SecondsSinceEpochJsonFieldDecoder implements JsonFieldDecoder { - private static final Set SUPPORTED_TYPES = ImmutableSet.of(TIME_MILLIS, TIME_WITH_TIME_ZONE, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS); + private static final Set SUPPORTED_TYPES = ImmutableSet.of(TIME_MILLIS, TIME_TZ_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS); private final DecoderColumnHandle columnHandle; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestCustomDateTimeJsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestCustomDateTimeJsonFieldDecoder.java index 7ec49613b853..a2c5d918c1f9 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestCustomDateTimeJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestCustomDateTimeJsonFieldDecoder.java @@ -23,7 +23,7 @@ import static io.trino.spi.type.DateTimeEncoding.packTimeWithTimeZone; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.spi.type.TimeZoneKey.getTimeZoneKeyForOffset; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; @@ -50,9 +50,9 @@ public void testDecode() timeTester.assertDecodedAs("\"15:13:18\"", TIME_MILLIS, 47_718_000_000_000_000L); timeJustHourTester.assertDecodedAs("\"15\"", TIME_MILLIS, 54_000_000_000_000_000L); timeJustHourTester.assertDecodedAs("15", TIME_MILLIS, 54_000_000_000_000_000L); - timeTester.assertDecodedAs("\"15:13:18\"", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(47_718_000_000_000L, 0)); - timeWTZTester.assertDecodedAs("\"15:13:18.123-04:00\"", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(54_798_123_000_000L, -4 * 60)); - timeWTZTester.assertDecodedAs("\"15:13:18.123+08:00\"", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(54_798_123_000_000L, 8 * 60)); + timeTester.assertDecodedAs("\"15:13:18\"", TIME_TZ_MILLIS, packTimeWithTimeZone(47_718_000_000_000L, 0)); + timeWTZTester.assertDecodedAs("\"15:13:18.123-04:00\"", TIME_TZ_MILLIS, packTimeWithTimeZone(54_798_123_000_000L, -4 * 60)); + timeWTZTester.assertDecodedAs("\"15:13:18.123+08:00\"", TIME_TZ_MILLIS, packTimeWithTimeZone(54_798_123_000_000L, 8 * 60)); dateTester.assertDecodedAs("\"02/2018/11\"", DATE, 17573); } @@ -65,8 +65,8 @@ public void testDecodeNulls() timeTester.assertDecodedAsNull("null", TIME_MILLIS); timeTester.assertMissingDecodedAsNull(TIME_MILLIS); - timeTester.assertDecodedAsNull("null", TIME_WITH_TIME_ZONE); - timeTester.assertMissingDecodedAsNull(TIME_WITH_TIME_ZONE); + timeTester.assertDecodedAsNull("null", TIME_TZ_MILLIS); + timeTester.assertMissingDecodedAsNull(TIME_TZ_MILLIS); timestampTester.assertDecodedAsNull("null", TIMESTAMP_MILLIS); timestampTester.assertMissingDecodedAsNull(TIMESTAMP_MILLIS); diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestISO8601JsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestISO8601JsonFieldDecoder.java index b84828e1587e..cd1a4f5ad067 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestISO8601JsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestISO8601JsonFieldDecoder.java @@ -20,7 +20,7 @@ import static io.trino.spi.type.DateTimeEncoding.packTimeWithTimeZone; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; @@ -45,16 +45,16 @@ public void testDecode() tester.assertDecodedAs("\"2018-02-11\"", DATE, 17573); tester.assertDecodedAs("\"2018-02-19T09:20:11Z\"", TIMESTAMP_TZ_MILLIS, packDateTimeWithZone(1519032011000L, UTC_KEY)); tester.assertDecodedAs("\"2018-02-19T12:20:11+03:00\"", TIMESTAMP_TZ_MILLIS, packDateTimeWithZone(1519032011000L, "+03:00")); - tester.assertDecodedAs("\"13:15:18Z\"", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(47_718_000_000_000L, 0)); - tester.assertDecodedAs("\"13:15:18+10:00\"", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(47_718_000_000_000L, 10 * 60)); - tester.assertDecodedAs("\"15:13:18.123-04:00\"", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(54_798_123_000_000L, -4 * 60)); - tester.assertDecodedAs("\"15:13:18.123+08:00\"", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(54_798_123_000_000L, 8 * 60)); + tester.assertDecodedAs("\"13:15:18Z\"", TIME_TZ_MILLIS, packTimeWithTimeZone(47_718_000_000_000L, 0)); + tester.assertDecodedAs("\"13:15:18+10:00\"", TIME_TZ_MILLIS, packTimeWithTimeZone(47_718_000_000_000L, 10 * 60)); + tester.assertDecodedAs("\"15:13:18.123-04:00\"", TIME_TZ_MILLIS, packTimeWithTimeZone(54_798_123_000_000L, -4 * 60)); + tester.assertDecodedAs("\"15:13:18.123+08:00\"", TIME_TZ_MILLIS, packTimeWithTimeZone(54_798_123_000_000L, 8 * 60)); } @Test public void testDecodeNulls() { - for (Type type : asList(DATE, TIME_MILLIS, TIME_WITH_TIME_ZONE, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { + for (Type type : asList(DATE, TIME_MILLIS, TIME_TZ_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { tester.assertDecodedAsNull("null", type); tester.assertMissingDecodedAsNull(type); } @@ -83,9 +83,9 @@ public void testDecodeInvalid() tester.assertInvalidInput("\"2018-02-19\"", TIME_MILLIS, "\\Qcould not parse value '2018-02-19' as 'time(3)' for column 'some_column'\\E"); tester.assertInvalidInput("\"2018-02-19Z\"", TIME_MILLIS, "\\Qcould not parse value '2018-02-19Z' as 'time(3)' for column 'some_column'\\E"); - tester.assertInvalidInput("\"2018-02-19T09:20:11\"", TIME_WITH_TIME_ZONE, "\\Qcould not parse value '2018-02-19T09:20:11' as 'time(3) with time zone' for column 'some_column'\\E"); - tester.assertInvalidInput("\"2018-02-19T09:20:11Z\"", TIME_WITH_TIME_ZONE, "\\Qcould not parse value '2018-02-19T09:20:11Z' as 'time(3) with time zone' for column 'some_column'\\E"); - tester.assertInvalidInput("\"09:20:11\"", TIME_WITH_TIME_ZONE, "\\Qcould not parse value '09:20:11' as 'time(3) with time zone' for column 'some_column'\\E"); - tester.assertInvalidInput("\"2018-02-19\"", TIME_WITH_TIME_ZONE, "\\Qcould not parse value '2018-02-19' as 'time(3) with time zone' for column 'some_column'\\E"); + tester.assertInvalidInput("\"2018-02-19T09:20:11\"", TIME_TZ_MILLIS, "\\Qcould not parse value '2018-02-19T09:20:11' as 'time(3) with time zone' for column 'some_column'\\E"); + tester.assertInvalidInput("\"2018-02-19T09:20:11Z\"", TIME_TZ_MILLIS, "\\Qcould not parse value '2018-02-19T09:20:11Z' as 'time(3) with time zone' for column 'some_column'\\E"); + tester.assertInvalidInput("\"09:20:11\"", TIME_TZ_MILLIS, "\\Qcould not parse value '09:20:11' as 'time(3) with time zone' for column 'some_column'\\E"); + tester.assertInvalidInput("\"2018-02-19\"", TIME_TZ_MILLIS, "\\Qcould not parse value '2018-02-19' as 'time(3) with time zone' for column 'some_column'\\E"); } } diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java index 2c6179c90390..079d453042dc 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java @@ -41,7 +41,7 @@ import static io.trino.spi.type.RealType.REAL; import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.TinyintType.TINYINT; @@ -152,14 +152,14 @@ public void testSupportedDataTypeValidation() for (String dataFormat : ImmutableSet.of("iso8601", "custom-date-time")) { singleColumnDecoder(DATE, dataFormat); singleColumnDecoder(TIME_MILLIS, dataFormat); - singleColumnDecoder(TIME_WITH_TIME_ZONE, dataFormat); + singleColumnDecoder(TIME_TZ_MILLIS, dataFormat); singleColumnDecoder(TIMESTAMP_MILLIS, dataFormat); singleColumnDecoder(TIMESTAMP_TZ_MILLIS, dataFormat); } for (String dataFormat : ImmutableSet.of("seconds-since-epoch", "milliseconds-since-epoch")) { singleColumnDecoder(TIME_MILLIS, dataFormat); - singleColumnDecoder(TIME_WITH_TIME_ZONE, dataFormat); + singleColumnDecoder(TIME_TZ_MILLIS, dataFormat); singleColumnDecoder(TIMESTAMP_MILLIS, dataFormat); singleColumnDecoder(TIMESTAMP_TZ_MILLIS, dataFormat); } @@ -172,7 +172,7 @@ public void testSupportedDataTypeValidation() // temporal types are not supported for default field decoder assertUnsupportedColumnTypeException(() -> singleColumnDecoder(DATE, null)); assertUnsupportedColumnTypeException(() -> singleColumnDecoder(TIME_MILLIS, null)); - assertUnsupportedColumnTypeException(() -> singleColumnDecoder(TIME_WITH_TIME_ZONE, null)); + assertUnsupportedColumnTypeException(() -> singleColumnDecoder(TIME_TZ_MILLIS, null)); assertUnsupportedColumnTypeException(() -> singleColumnDecoder(TIMESTAMP_MILLIS, null)); assertUnsupportedColumnTypeException(() -> singleColumnDecoder(TIMESTAMP_TZ_MILLIS, null)); diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestMillisecondsSinceEpochJsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestMillisecondsSinceEpochJsonFieldDecoder.java index a42f62639cd1..b36acf2e3038 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestMillisecondsSinceEpochJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestMillisecondsSinceEpochJsonFieldDecoder.java @@ -21,7 +21,7 @@ import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; import static io.trino.spi.type.DateTimeEncoding.packTimeWithTimeZone; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; @@ -36,8 +36,8 @@ public void testDecode() { tester.assertDecodedAs("33701000", TIME_MILLIS, 33_701_000_000_000_000L); tester.assertDecodedAs("\"33701000\"", TIME_MILLIS, 33_701_000_000_000_000L); - tester.assertDecodedAs("33701000", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(33_701_000_000_000L, 0)); - tester.assertDecodedAs("\"33701000\"", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(33_701_000_000_000L, 0)); + tester.assertDecodedAs("33701000", TIME_TZ_MILLIS, packTimeWithTimeZone(33_701_000_000_000L, 0)); + tester.assertDecodedAs("\"33701000\"", TIME_TZ_MILLIS, packTimeWithTimeZone(33_701_000_000_000L, 0)); tester.assertDecodedAs("1519032101123", TIMESTAMP_MILLIS, 1_519_032_101_123_000L); tester.assertDecodedAs("\"1519032101123\"", TIMESTAMP_MILLIS, 1_519_032_101_123_000L); tester.assertDecodedAs("1519032101123", TIMESTAMP_TZ_MILLIS, packDateTimeWithZone(1519032101123L, UTC_KEY)); @@ -47,7 +47,7 @@ public void testDecode() @Test public void testDecodeNulls() { - for (Type type : asList(TIME_MILLIS, TIME_WITH_TIME_ZONE, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { + for (Type type : asList(TIME_MILLIS, TIME_TZ_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { tester.assertDecodedAsNull("null", type); tester.assertMissingDecodedAsNull(type); } @@ -56,7 +56,7 @@ public void testDecodeNulls() @Test public void testDecodeInvalid() { - for (Type type : asList(TIME_MILLIS, TIME_WITH_TIME_ZONE, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { + for (Type type : asList(TIME_MILLIS, TIME_TZ_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { tester.assertInvalidInput("{}", type, "could not parse non-value node as '.*' for column 'some_column'"); tester.assertInvalidInput("[]", type, "could not parse non-value node as '.*' for column 'some_column'"); tester.assertInvalidInput("[10]", type, "could not parse non-value node as '.*' for column 'some_column'"); @@ -68,7 +68,7 @@ public void testDecodeInvalid() // TIME specific range checks tester.assertInvalidInput("-1", TIME_MILLIS, "\\Qcould not parse value '-1' as 'time(3)' for column 'some_column'\\E"); tester.assertInvalidInput("" + TimeUnit.DAYS.toMillis(1) + 1, TIME_MILLIS, "\\Qcould not parse value '864000001' as 'time(3)' for column 'some_column'\\E"); - tester.assertInvalidInput("-1", TIME_WITH_TIME_ZONE, "\\Qcould not parse value '-1' as 'time(3) with time zone' for column 'some_column'\\E"); - tester.assertInvalidInput("" + TimeUnit.DAYS.toMillis(1) + 1, TIME_WITH_TIME_ZONE, "\\Qcould not parse value '864000001' as 'time(3) with time zone' for column 'some_column'\\E"); + tester.assertInvalidInput("-1", TIME_TZ_MILLIS, "\\Qcould not parse value '-1' as 'time(3) with time zone' for column 'some_column'\\E"); + tester.assertInvalidInput("" + TimeUnit.DAYS.toMillis(1) + 1, TIME_TZ_MILLIS, "\\Qcould not parse value '864000001' as 'time(3) with time zone' for column 'some_column'\\E"); } } diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestSecondsSinceEpochJsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestSecondsSinceEpochJsonFieldDecoder.java index a003db95bb4f..0ea0e43c492f 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestSecondsSinceEpochJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestSecondsSinceEpochJsonFieldDecoder.java @@ -21,7 +21,7 @@ import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; import static io.trino.spi.type.DateTimeEncoding.packTimeWithTimeZone; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; @@ -36,8 +36,8 @@ public void testDecode() { tester.assertDecodedAs("33701", TIME_MILLIS, 33_701_000_000_000_000L); tester.assertDecodedAs("\"33701\"", TIME_MILLIS, 33_701_000_000_000_000L); - tester.assertDecodedAs("33701", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(33_701_000_000_000L, 0)); - tester.assertDecodedAs("\"33701\"", TIME_WITH_TIME_ZONE, packTimeWithTimeZone(33_701_000_000_000L, 0)); + tester.assertDecodedAs("33701", TIME_TZ_MILLIS, packTimeWithTimeZone(33_701_000_000_000L, 0)); + tester.assertDecodedAs("\"33701\"", TIME_TZ_MILLIS, packTimeWithTimeZone(33_701_000_000_000L, 0)); tester.assertDecodedAs("1519032101", TIMESTAMP_MILLIS, 1_519_032_101_000_000L); tester.assertDecodedAs("\"1519032101\"", TIMESTAMP_MILLIS, 1_519_032_101_000_000L); tester.assertDecodedAs("" + (Long.MAX_VALUE / 1_000_000), TIMESTAMP_MILLIS, Long.MAX_VALUE / 1_000_000 * 1_000_000); @@ -49,7 +49,7 @@ public void testDecode() @Test public void testDecodeNulls() { - for (Type type : asList(TIME_MILLIS, TIME_WITH_TIME_ZONE, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { + for (Type type : asList(TIME_MILLIS, TIME_TZ_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { tester.assertDecodedAsNull("null", type); tester.assertMissingDecodedAsNull(type); } @@ -58,7 +58,7 @@ public void testDecodeNulls() @Test public void testDecodeInvalid() { - for (Type type : asList(TIME_MILLIS, TIME_WITH_TIME_ZONE, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { + for (Type type : asList(TIME_MILLIS, TIME_TZ_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_TZ_MILLIS)) { tester.assertInvalidInput("{}", type, "could not parse non-value node as '.*' for column 'some_column'"); tester.assertInvalidInput("[]", type, "could not parse non-value node as '.*' for column 'some_column'"); tester.assertInvalidInput("[10]", type, "could not parse non-value node as '.*' for column 'some_column'"); @@ -72,7 +72,7 @@ public void testDecodeInvalid() // TIME specific range checks tester.assertInvalidInput("-1", TIME_MILLIS, "\\Qcould not parse value '-1' as 'time(3)' for column 'some_column'\\E"); tester.assertInvalidInput("" + TimeUnit.DAYS.toSeconds(1) + 1, TIME_MILLIS, "\\Qcould not parse value '864001' as 'time(3)' for column 'some_column'\\E"); - tester.assertInvalidInput("-1", TIME_WITH_TIME_ZONE, "\\Qcould not parse value '-1' as 'time(3) with time zone' for column 'some_column'\\E"); - tester.assertInvalidInput("" + TimeUnit.DAYS.toSeconds(1) + 1, TIME_WITH_TIME_ZONE, "\\Qcould not parse value '864001' as 'time(3) with time zone' for column 'some_column'\\E"); + tester.assertInvalidInput("-1", TIME_TZ_MILLIS, "\\Qcould not parse value '-1' as 'time(3) with time zone' for column 'some_column'\\E"); + tester.assertInvalidInput("" + TimeUnit.DAYS.toSeconds(1) + 1, TIME_TZ_MILLIS, "\\Qcould not parse value '864001' as 'time(3) with time zone' for column 'some_column'\\E"); } } diff --git a/plugin/trino-accumulo-iterators/pom.xml b/plugin/trino-accumulo-iterators/pom.xml index 31ba78156857..c9c9a49f8330 100644 --- a/plugin/trino-accumulo-iterators/pom.xml +++ b/plugin/trino-accumulo-iterators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-accumulo/pom.xml b/plugin/trino-accumulo/pom.xml index 01489873d2eb..6f699b857d34 100644 --- a/plugin/trino-accumulo/pom.xml +++ b/plugin/trino-accumulo/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/index/Indexer.java b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/index/Indexer.java index 867dcb098340..f33737b8b541 100644 --- a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/index/Indexer.java +++ b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/index/Indexer.java @@ -13,11 +13,13 @@ */ package io.trino.plugin.accumulo.index; +import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; +import com.google.common.collect.Table; import com.google.common.primitives.UnsignedBytes; import io.trino.plugin.accumulo.Types; import io.trino.plugin.accumulo.iterators.MaxByteArrayCombiner; @@ -150,7 +152,7 @@ public Indexer( indexWriter = connector.createBatchWriter(table.getIndexTableName(), writerConfig); ImmutableMultimap.Builder indexColumnsBuilder = ImmutableMultimap.builder(); - Map> indexColumnTypesBuilder = new HashMap<>(); + Table indexColumnTypesBuilder = HashBasedTable.create(); // Initialize metadata table.getColumns().forEach(columnHandle -> { @@ -163,17 +165,12 @@ public Indexer( // Create a mapping for this column's Trino type, again creating a new one for the // family if necessary - Map types = indexColumnTypesBuilder.get(family); - if (types == null) { - types = new HashMap<>(); - indexColumnTypesBuilder.put(family, types); - } - types.put(qualifier, columnHandle.getType()); + indexColumnTypesBuilder.put(family, qualifier, columnHandle.getType()); } }); indexColumns = indexColumnsBuilder.build(); - indexColumnTypes = ImmutableMap.copyOf(indexColumnTypesBuilder); + indexColumnTypes = ImmutableMap.copyOf(indexColumnTypesBuilder.rowMap()); // If there are no indexed columns, throw an exception if (indexColumns.isEmpty()) { diff --git a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/serializers/LexicoderRowSerializer.java b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/serializers/LexicoderRowSerializer.java index ea8726a1b412..bdb887df1f7d 100644 --- a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/serializers/LexicoderRowSerializer.java +++ b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/serializers/LexicoderRowSerializer.java @@ -13,6 +13,8 @@ */ package io.trino.plugin.accumulo.serializers; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; import io.airlift.slice.Slice; import io.trino.plugin.accumulo.Types; import io.trino.spi.TrinoException; @@ -63,7 +65,7 @@ public class LexicoderRowSerializer private static final Map> LIST_LEXICODERS = new HashMap<>(); private static final Map> MAP_LEXICODERS = new HashMap<>(); - private final Map> familyQualifierColumnMap = new HashMap<>(); + private final Table familyQualifierColumnMap = HashBasedTable.create(); private final Map columnValues = new HashMap<>(); private final Text rowId = new Text(); private final Text family = new Text(); @@ -106,13 +108,7 @@ public void setRowOnly(boolean rowOnly) public void setMapping(String name, String family, String qualifier) { columnValues.put(name, null); - Map qualifierToNameMap = familyQualifierColumnMap.get(family); - if (qualifierToNameMap == null) { - qualifierToNameMap = new HashMap<>(); - familyQualifierColumnMap.put(family, qualifierToNameMap); - } - - qualifierToNameMap.put(qualifier, name); + familyQualifierColumnMap.put(family, qualifier, name); } @Override @@ -141,7 +137,7 @@ public void deserialize(Entry entry) } value.set(entry.getValue().get()); - columnValues.put(familyQualifierColumnMap.get(family.toString()).get(qualifier.toString()), value.copyBytes()); + columnValues.put(familyQualifierColumnMap.get(family.toString(), qualifier.toString()), value.copyBytes()); } @Override diff --git a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/serializers/StringRowSerializer.java b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/serializers/StringRowSerializer.java index 2088ed2663d2..cbb21b5db0c4 100644 --- a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/serializers/StringRowSerializer.java +++ b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/serializers/StringRowSerializer.java @@ -13,6 +13,8 @@ */ package io.trino.plugin.accumulo.serializers; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; import io.airlift.slice.Slice; import io.trino.plugin.accumulo.Types; import io.trino.spi.TrinoException; @@ -51,7 +53,7 @@ public class StringRowSerializer implements AccumuloRowSerializer { - private final Map> familyQualifierColumnMap = new HashMap<>(); + private final Table familyQualifierColumnMap = HashBasedTable.create(); private final Map columnValues = new HashMap<>(); private final Text rowId = new Text(); private final Text family = new Text(); @@ -77,13 +79,7 @@ public void setRowOnly(boolean rowOnly) public void setMapping(String name, String family, String qualifier) { columnValues.put(name, null); - Map qualifierColumnMap = familyQualifierColumnMap.get(family); - if (qualifierColumnMap == null) { - qualifierColumnMap = new HashMap<>(); - familyQualifierColumnMap.put(family, qualifierColumnMap); - } - - qualifierColumnMap.put(qualifier, name); + familyQualifierColumnMap.put(family, qualifier, name); } @Override @@ -112,7 +108,7 @@ public void deserialize(Entry entry) } value.set(entry.getValue().get()); - columnValues.put(familyQualifierColumnMap.get(family.toString()).get(qualifier.toString()), value.toString()); + columnValues.put(familyQualifierColumnMap.get(family.toString(), qualifier.toString()), value.toString()); } @Override diff --git a/plugin/trino-atop/pom.xml b/plugin/trino-atop/pom.xml index 2496137e2d1a..89602743aa43 100644 --- a/plugin/trino-atop/pom.xml +++ b/plugin/trino-atop/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index 362c60ff16b9..01a39eb365d2 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java index b2659249e582..4833138c1af7 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java @@ -923,7 +923,7 @@ private void addColumn(ConnectorSession session, RemoteTableName table, ColumnMe } } - private void addColumn(ConnectorSession session, Connection connection, RemoteTableName table, ColumnMetadata column) + protected void addColumn(ConnectorSession session, Connection connection, RemoteTableName table, ColumnMetadata column) throws SQLException { String columnName = column.getName(); diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index 840a7d6860ed..b0861de01a3a 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-blackhole/pom.xml b/plugin/trino-blackhole/pom.xml index 2fd2968b67cf..1891abbbe708 100644 --- a/plugin/trino-blackhole/pom.xml +++ b/plugin/trino-blackhole/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index 8383ff493f23..2f81840f838d 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index 0bdfef87437f..c7a111fe7020 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index 492c73f3ee54..c6ba0940591e 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index 41f506ae0202..d0d2b39bc530 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index 15ac5d51b983..bf88aa7b92f4 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-example-http/pom.xml b/plugin/trino-example-http/pom.xml index d38316fd6a10..7544fe1c2fe5 100644 --- a/plugin/trino-example-http/pom.xml +++ b/plugin/trino-example-http/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index 8aee21c31ae0..6c3b089d213d 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-hdfs/pom.xml b/plugin/trino-exchange-hdfs/pom.xml index 71701a6b5bbf..3eed1f8e3c77 100644 --- a/plugin/trino-exchange-hdfs/pom.xml +++ b/plugin/trino-exchange-hdfs/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index 34d9df5c4f7f..779d8b880bb7 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-google-sheets/pom.xml b/plugin/trino-google-sheets/pom.xml index cea2b1241230..b45bd17985de 100644 --- a/plugin/trino-google-sheets/pom.xml +++ b/plugin/trino-google-sheets/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index e33adc49da11..2a0817385b66 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index 668e2e9c3893..09465964bf0e 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java index 8533de0bd7c7..32e17823dad4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java @@ -13,6 +13,7 @@ */ package io.trino.plugin.hive.metastore.cache; +import com.google.common.cache.Cache; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; @@ -70,6 +71,7 @@ import java.util.concurrent.Executor; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import static com.google.common.base.Functions.identity; import static com.google.common.base.Preconditions.checkArgument; @@ -82,6 +84,7 @@ import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap; import static com.google.common.collect.Maps.immutableEntry; import static com.google.common.collect.Streams.stream; +import static io.trino.collect.cache.CacheUtils.uncheckedCacheGet; import static io.trino.plugin.hive.HivePartitionManager.extractPartitionValues; import static io.trino.plugin.hive.metastore.HivePartitionName.hivePartitionName; import static io.trino.plugin.hive.metastore.HiveTableName.hiveTableName; @@ -271,9 +274,9 @@ protected CachingHiveMetastore(HiveMetastore delegate, OptionalLong expiresAfter grantedPrincipalsCache = cacheFactory.buildCache(this::loadPrincipals); configValuesCache = cacheFactory.buildCache(this::loadConfigValue); - partitionStatisticsCache = partitionCacheFactory.buildCache(this::loadPartitionColumnStatistics, this::loadPartitionsColumnStatistics); + partitionStatisticsCache = partitionCacheFactory.buildBulkCache(this::loadPartitionsColumnStatistics); partitionFilterCache = partitionCacheFactory.buildCache(this::loadPartitionNamesByFilter); - partitionCache = partitionCacheFactory.buildCache(this::loadPartitionByName, this::loadPartitionsByNames); + partitionCache = partitionCacheFactory.buildBulkCache(this::loadPartitionsByNames); } private static LoadingCache neverCache(com.google.common.base.Function loader) @@ -281,9 +284,9 @@ private static LoadingCache neverCache(com.google.common.base.Funct return buildCache(OptionalLong.of(0), OptionalLong.empty(), Optional.empty(), 0, StatsRecording.DISABLED, loader); } - private static LoadingCache neverCache(Function loader, Function, Map> bulkLoader) + private static LoadingCache neverCache(Function, Map> bulkLoader) { - return buildCache(OptionalLong.of(0), 0, StatsRecording.DISABLED, loader, bulkLoader); + return buildCache(OptionalLong.of(0), 0, StatsRecording.DISABLED, bulkLoader); } @Managed @@ -324,6 +327,11 @@ private static V get(LoadingCache cache, K key) } } + private static V get(Cache cache, K key, Supplier loader) + { + return uncheckedCacheGet(cache, key, loader); + } + private static Map getAll(LoadingCache cache, Iterable keys) { try { @@ -384,7 +392,7 @@ private Optional
loadTable(HiveTableName hiveTableName) @Override public PartitionStatistics getTableStatistics(Table table) { - return get(tableStatisticsCache, hiveTableName(table.getDatabaseName(), table.getTableName())); + return get(tableStatisticsCache, hiveTableName(table.getDatabaseName(), table.getTableName()), () -> delegate.getTableStatistics(table)); } private PartitionStatistics loadTableColumnStatistics(HiveTableName tableName) @@ -406,17 +414,6 @@ public Map getPartitionStatistics(Table table, List .collect(toImmutableMap(entry -> entry.getKey().getPartitionName().orElseThrow(), Entry::getValue)); } - private PartitionStatistics loadPartitionColumnStatistics(HivePartitionName partition) - { - HiveTableName tableName = partition.getHiveTableName(); - String partitionName = partition.getPartitionName().orElseThrow(); - Table table = getExistingTable(tableName.getDatabaseName(), tableName.getTableName()); - Map partitionStatistics = delegate.getPartitionStatistics( - table, - ImmutableList.of(getExistingPartition(table, partition.getPartitionValues()))); - return partitionStatistics.get(partitionName); - } - private Map loadPartitionsColumnStatistics(Iterable keys) { SetMultimap tablePartitions = stream(keys) @@ -741,7 +738,7 @@ private List getExistingPartitionsByNames(Table table, List p @Override public Optional getPartition(Table table, List partitionValues) { - return get(partitionCache, hivePartitionName(hiveTableName(table.getDatabaseName(), table.getTableName()), partitionValues)); + return get(partitionCache, hivePartitionName(hiveTableName(table.getDatabaseName(), table.getTableName()), partitionValues), () -> delegate.getPartition(table, partitionValues)); } @Override @@ -777,13 +774,6 @@ public Map> getPartitionsByNames(Table table, List loadPartitionByName(HivePartitionName partitionName) - { - HiveTableName hiveTableName = partitionName.getHiveTableName(); - return getTable(hiveTableName.getDatabaseName(), hiveTableName.getTableName()) - .flatMap(table -> delegate.getPartition(table, partitionName.getPartitionValues())); - } - private Map> loadPartitionsByNames(Iterable partitionNames) { requireNonNull(partitionNames, "partitionNames is null"); @@ -1112,7 +1102,7 @@ private interface CacheFactory { LoadingCache buildCache(com.google.common.base.Function loader); - LoadingCache buildCache(com.google.common.base.Function loader, Function, Map> bulkLoader); + LoadingCache buildBulkCache(Function, Map> bulkLoader); } private static CacheFactory cacheFactory( @@ -1131,10 +1121,10 @@ public LoadingCache buildCache(com.google.common.base.Function LoadingCache buildCache(com.google.common.base.Function loader, Function, Map> bulkLoader) + public LoadingCache buildBulkCache(Function, Map> bulkLoader) { // disable refresh since it can't use the bulk loading and causes too many requests - return CachingHiveMetastore.buildCache(expiresAfterWriteMillis, maximumSize, statsRecording, loader, bulkLoader); + return CachingHiveMetastore.buildCache(expiresAfterWriteMillis, maximumSize, statsRecording, bulkLoader); } }; } @@ -1150,9 +1140,9 @@ public LoadingCache buildCache(com.google.common.base.Function LoadingCache buildCache(com.google.common.base.Function loader, Function, Map> bulkLoader) + public LoadingCache buildBulkCache(Function, Map> bulkLoader) { - return neverCache(loader, bulkLoader); + return neverCache(bulkLoader); } }; } @@ -1188,17 +1178,15 @@ private static LoadingCache buildCache( OptionalLong expiresAfterWriteMillis, long maximumSize, StatsRecording statsRecording, - Function loader, Function, Map> bulkLoader) { - requireNonNull(loader, "loader is null"); requireNonNull(bulkLoader, "bulkLoader is null"); CacheLoader cacheLoader = new CacheLoader<>() { @Override public V load(K key) { - return loader.apply(key); + throw new IllegalStateException("loadAll should be used instead"); } @Override 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/util/RetryDriver.java index b0a938e75fed..f3e5cacb6543 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/util/RetryDriver.java @@ -130,6 +130,11 @@ public V run(String callableName, Callable callable) return callable.call(); } catch (Exception e) { + // Immediately stop retry attempts once an interrupt has been received + if (e instanceof InterruptedException || Thread.currentThread().isInterrupted()) { + addSuppressed(e, suppressedExceptions); + throw e; + } for (Class clazz : stopOnExceptions) { if (clazz.isInstance(e)) { addSuppressed(e, suppressedExceptions); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java index 716e761b9f44..11d36053b434 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java @@ -47,6 +47,7 @@ public enum Methods GET_TABLE_WITH_PARAMETER, GET_TABLE_STATISTICS, REPLACE_TABLE, + DROP_TABLE, } private final HiveMetastore delegate; @@ -142,7 +143,8 @@ public void createTable(Table table, PrincipalPrivileges principalPrivileges) @Override public void dropTable(String databaseName, String tableName, boolean deleteData) { - throw new UnsupportedOperationException(); + methodInvocations.add(Methods.DROP_TABLE); + delegate.dropTable(databaseName, tableName, deleteData); } @Override 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 31c96c28debe..8dbc23be130c 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 @@ -492,11 +492,14 @@ public void testGetTableStatistics() assertEquals(metastore.getTableStatistics(table), TEST_STATS); assertEquals(mockClient.getAccessCount(), 2); - assertEquals(metastore.getTableStatisticsStats().getRequestCount(), 1); - assertEquals(metastore.getTableStatisticsStats().getHitRate(), 0.0); + assertEquals(metastore.getTableStatistics(table), TEST_STATS); + assertEquals(mockClient.getAccessCount(), 2); - assertEquals(metastore.getTableStats().getRequestCount(), 2); - assertEquals(metastore.getTableStats().getHitRate(), 0.5); + assertEquals(metastore.getTableStatisticsStats().getRequestCount(), 2); + assertEquals(metastore.getTableStatisticsStats().getHitRate(), 0.5); + + assertEquals(metastore.getTableStats().getRequestCount(), 1); + assertEquals(metastore.getTableStats().getHitRate(), 0.0); } @Test @@ -513,11 +516,14 @@ public void testGetPartitionStatistics() assertEquals(metastore.getPartitionStatistics(table, ImmutableList.of(partition)), ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); assertEquals(mockClient.getAccessCount(), 3); - assertEquals(metastore.getPartitionStatisticsStats().getRequestCount(), 1); - assertEquals(metastore.getPartitionStatisticsStats().getHitRate(), 0.0); + assertEquals(metastore.getPartitionStatistics(table, ImmutableList.of(partition)), ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); + assertEquals(mockClient.getAccessCount(), 3); - assertEquals(metastore.getTableStats().getRequestCount(), 3); - assertEquals(metastore.getTableStats().getHitRate(), 2.0 / 3); + assertEquals(metastore.getPartitionStatisticsStats().getRequestCount(), 2); + assertEquals(metastore.getPartitionStatisticsStats().getHitRate(), 0.5); + + assertEquals(metastore.getTableStats().getRequestCount(), 2); + assertEquals(metastore.getTableStats().getHitRate(), 1.0 / 2); assertEquals(metastore.getPartitionStats().getRequestCount(), 2); assertEquals(metastore.getPartitionStats().getHitRate(), 0.5); 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 831317930dcc..ca4bd44b5726 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 @@ -129,7 +129,7 @@ public void testPruneSimplePartitionLikeFilter() assertDistributedPlan( "SELECT * FROM table_str_partitioned WHERE str_part LIKE 't%'", output( - filter("\"$like\"(STR_PART, \"$literal$\"(from_base64('DgAAAFZBUklBQkxFX1dJRFRIAQAAAAcAAAAABwAAAAIAAAB0JQA=')))", + filter("\"$like\"(STR_PART, \"$literal$\"(from_base64('DgAAAFZBUklBQkxFX1dJRFRIAQAAAAEAAAAHAAAAAAcAAAACAAAAdCUA')))", tableScan("table_str_partitioned", Map.of("INT_COL", "int_col", "STR_PART", "str_part"))))); } @@ -151,12 +151,12 @@ public void testPrunePartitionLikeFilter() .left( exchange(REMOTE, REPARTITION, project( - filter("\"$like\"(L_STR_PART, \"$literal$\"(from_base64('DgAAAFZBUklBQkxFX1dJRFRIAQAAAAcAAAAABwAAAAIAAAB0JQA=')))", + filter("\"$like\"(L_STR_PART, \"$literal$\"(from_base64('DgAAAFZBUklBQkxFX1dJRFRIAQAAAAEAAAAHAAAAAAcAAAACAAAAdCUA')))", tableScan("table_str_partitioned", Map.of("L_INT_COL", "int_col", "L_STR_PART", "str_part")))))) .right(exchange(LOCAL, exchange(REMOTE, REPARTITION, project( - filter("R_STR_COL IN ('three', CAST('two' AS varchar(5))) AND \"$like\"(R_STR_COL, \"$literal$\"(from_base64('DgAAAFZBUklBQkxFX1dJRFRIAQAAAAcAAAAABwAAAAIAAAB0JQA=')))", + filter("R_STR_COL IN ('three', CAST('two' AS varchar(5))) AND \"$like\"(R_STR_COL, \"$literal$\"(from_base64('DgAAAFZBUklBQkxFX1dJRFRIAQAAAAEAAAAHAAAAAAcAAAACAAAAdCUA')))", tableScan("table_unpartitioned", Map.of("R_STR_COL", "str_col", "R_INT_COL", "int_col")))))))))); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/AbstractTestParquetReader.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/AbstractTestParquetReader.java index ba5b3ac39460..0c8717707986 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/AbstractTestParquetReader.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/AbstractTestParquetReader.java @@ -35,6 +35,7 @@ import org.apache.hadoop.hive.common.type.Date; import org.apache.hadoop.hive.common.type.HiveDecimal; import org.apache.hadoop.hive.common.type.Timestamp; +import org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.primitive.JavaHiveDecimalObjectInspector; import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo; @@ -875,90 +876,6 @@ private static long maxPrecision(int numBytes) return Math.round(Math.floor(Math.log10(Math.pow(2, 8 * numBytes - 1) - 1))); } - @Test - public void testDecimalBackedByINT32() - throws Exception - { - for (int precision = 4; precision <= MAX_PRECISION_INT32; precision++) { - int scale = ThreadLocalRandom.current().nextInt(precision); - MessageType parquetSchema = parseMessageType(format("message hive_decimal { optional INT32 test (DECIMAL(%d, %d)); }", precision, scale)); - int expectedPrecision = precision; - - ImmutableList.Builder writeValues = ImmutableList.builder(); - int start = toIntExact(-1 * (Math.round(Math.pow(10, precision)) - 1)); - int end = toIntExact(Math.round(Math.pow(10, precision))); - int step = Math.max((end - start) / 2_000, 1); - for (int value = start; value < end; value += step) { - writeValues.add(value); - } - List intValues = writeValues.build(); - - tester.testRoundTrip( - javaIntObjectInspector, - intValues, - intValues.stream() - .map(value -> SqlDecimal.of(value, expectedPrecision, scale)) - .collect(Collectors.toList()), - getOnlyElement(TEST_COLUMN), - createDecimalType(precision, scale), - Optional.of(parquetSchema), - ParquetSchemaOptions.withIntegerBackedDecimals()); - - tester.testRoundTrip( - javaIntObjectInspector, - intValues, - intValues.stream() - .map(value -> SqlDecimal.of(value, MAX_PRECISION, scale)) - .collect(Collectors.toList()), - getOnlyElement(TEST_COLUMN), - createDecimalType(MAX_PRECISION, scale), - Optional.of(parquetSchema), - ParquetSchemaOptions.withIntegerBackedDecimals()); - } - } - - @Test - public void testDecimalBackedByINT64() - throws Exception - { - for (int precision = 4; precision <= MAX_PRECISION_INT64; precision++) { - int scale = ThreadLocalRandom.current().nextInt(precision); - MessageType parquetSchema = parseMessageType(format("message hive_decimal { optional INT64 test (DECIMAL(%d, %d)); }", precision, scale)); - int expectedPrecision = precision; - - ImmutableList.Builder writeValues = ImmutableList.builder(); - long start = -1 * (Math.round(Math.pow(10, precision)) - 1); - long end = Math.round(Math.pow(10, precision)); - long step = Math.max((end - start) / 2_000, 1); - for (long value = start; value < end; value += step) { - writeValues.add(value); - } - List longValues = writeValues.build(); - - tester.testRoundTrip( - javaLongObjectInspector, - longValues, - longValues.stream() - .map(value -> SqlDecimal.of(value, expectedPrecision, scale)) - .collect(Collectors.toList()), - getOnlyElement(TEST_COLUMN), - createDecimalType(precision, scale), - Optional.of(parquetSchema), - ParquetSchemaOptions.withIntegerBackedDecimals()); - - tester.testRoundTrip( - javaLongObjectInspector, - longValues, - longValues.stream() - .map(value -> SqlDecimal.of(value, MAX_PRECISION, scale)) - .collect(Collectors.toList()), - getOnlyElement(TEST_COLUMN), - createDecimalType(MAX_PRECISION, scale), - Optional.of(parquetSchema), - ParquetSchemaOptions.withIntegerBackedDecimals()); - } - } - @Test public void testParquetShortDecimalWriteToTrinoDecimalWithNonMatchingScale() throws Exception @@ -967,35 +884,164 @@ public void testParquetShortDecimalWriteToTrinoDecimalWithNonMatchingScale() tester.testRoundTrip(javaLongObjectInspector, ImmutableList.of(10L), ImmutableList.of(SqlDecimal.of(100L, 10, 2)), createDecimalType(10, 2), Optional.of(parquetSchema)); } - @Test - public void testDecimalBackedByFixedLenByteArray() + @Test(dataProvider = "testDecimalInputProvider") + public void testDecimals(DecimalInput decimalInput) throws Exception { - for (int precision = 1; precision < MAX_PRECISION; precision++) { + for (int precision = 1; precision <= decimalInput.getMaxSupportedPrecision(); precision++) { int scale = ThreadLocalRandom.current().nextInt(precision); + MessageType parquetSchema = parseMessageType(format( + "message hive_decimal { optional %s test (DECIMAL(%d, %d)); }", + decimalInput.getPrimitiveTypeName(precision), + precision, + scale)); ImmutableList.Builder expectedValues = ImmutableList.builder(); ImmutableList.Builder expectedValuesMaxPrecision = ImmutableList.builder(); - ImmutableList.Builder writeValues = ImmutableList.builder(); + ImmutableList.Builder writeValuesBuilder = ImmutableList.builder(); BigInteger start = BigInteger.valueOf(10).pow(precision).subtract(ONE).negate(); BigInteger end = BigInteger.valueOf(10).pow(precision); - BigInteger step = BigInteger.valueOf(1).max(end.subtract(start).divide(BigInteger.valueOf(1_000))); + BigInteger step = BigInteger.valueOf(1).max(end.subtract(start).divide(BigInteger.valueOf(1_500))); for (BigInteger value = start; value.compareTo(end) < 0; value = value.add(step)) { - writeValues.add(HiveDecimal.create(value, scale)); + writeValuesBuilder.add(decimalInput.convertToWriteValue(value, scale)); expectedValues.add(new SqlDecimal(value, precision, scale)); expectedValuesMaxPrecision.add(new SqlDecimal(value, MAX_PRECISION, scale)); } - tester.testRoundTrip(new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(precision, scale)), - writeValues.build(), + List writeValues = writeValuesBuilder.build(); + tester.testRoundTrip( + decimalInput.getParquetObjectInspector(precision, scale), + writeValues, expectedValues.build(), - createDecimalType(precision, scale)); - tester.testRoundTrip(new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(precision, scale)), - writeValues.build(), + createDecimalType(precision, scale), + Optional.of(parquetSchema)); + tester.testRoundTrip( + decimalInput.getParquetObjectInspector(precision, scale), + writeValues, expectedValuesMaxPrecision.build(), - createDecimalType(MAX_PRECISION, scale)); + createDecimalType(MAX_PRECISION, scale), + Optional.of(parquetSchema)); } } + @DataProvider + public Object[][] testDecimalInputProvider() + { + return Arrays.stream(DecimalInput.values()) + .collect(toDataProvider()); + } + + private enum DecimalInput + { + INT32 { + @Override + String getPrimitiveTypeName(int precision) + { + return "INT32"; + } + + @Override + int getMaxSupportedPrecision() + { + return MAX_PRECISION_INT32; + } + + @Override + ObjectInspector getParquetObjectInspector(int precision, int scale) + { + return javaIntObjectInspector; + } + + @Override + Object convertToWriteValue(BigInteger value, int scale) + { + return value.intValueExact(); + } + }, + INT64 { + @Override + String getPrimitiveTypeName(int precision) + { + return "INT64"; + } + + @Override + int getMaxSupportedPrecision() + { + return MAX_PRECISION_INT64; + } + + @Override + ObjectInspector getParquetObjectInspector(int precision, int scale) + { + return javaLongObjectInspector; + } + + @Override + Object convertToWriteValue(BigInteger value, int scale) + { + return value.longValueExact(); + } + }, + BINARY { + @Override + String getPrimitiveTypeName(int precision) + { + return "BINARY"; + } + + @Override + int getMaxSupportedPrecision() + { + return MAX_PRECISION; + } + + @Override + ObjectInspector getParquetObjectInspector(int precision, int scale) + { + return new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(precision, scale)); + } + + @Override + Object convertToWriteValue(BigInteger value, int scale) + { + return HiveDecimal.create(value, scale); + } + }, + FIXED_LEN_BYTE_ARRAY { + @Override + String getPrimitiveTypeName(int precision) + { + return format("FIXED_LEN_BYTE_ARRAY(%d)", ParquetHiveSerDe.PRECISION_TO_BYTE_COUNT[precision - 1]); + } + + @Override + int getMaxSupportedPrecision() + { + return MAX_PRECISION; + } + + @Override + ObjectInspector getParquetObjectInspector(int precision, int scale) + { + return new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(precision, scale)); + } + + @Override + Object convertToWriteValue(BigInteger value, int scale) + { + return HiveDecimal.create(value, scale); + } + }; + + abstract String getPrimitiveTypeName(int precision); + + abstract int getMaxSupportedPrecision(); + + abstract ObjectInspector getParquetObjectInspector(int precision, int scale); + + abstract Object convertToWriteValue(BigInteger value, int scale); + } + @Test public void testParquetLongDecimalWriteToTrinoDecimalWithNonMatchingScale() throws Exception diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java index 1826b865f248..982e4a8b8154 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java @@ -357,10 +357,8 @@ public void testParquetLongFixedLenByteArrayWithTrinoShortDecimal( if (overflows(new BigDecimal(writeValue).unscaledValue(), schemaPrecision)) { @Language("SQL") String query = format("SELECT * FROM tpch.%s", tableName); @Language("RegExp") String expectedMessage = format( - "Could not read unscaled value %s into decimal\\(%d,%d\\) from column .*", - new BigDecimal(writeValue).unscaledValue(), - schemaPrecision, - schemaScale); + "Could not read unscaled value %s into a short decimal from column .*", + new BigDecimal(writeValue).unscaledValue()); assertQueryFails(optimizedParquetReaderEnabled(false), query, expectedMessage); assertQueryFails(optimizedParquetReaderEnabled(true), query, expectedMessage); diff --git a/plugin/trino-http-event-listener/pom.xml b/plugin/trino-http-event-listener/pom.xml index 2632745e47f2..5562464abc3b 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 - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 95e2dcebdac3..d9bfda845beb 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -5,7 +5,7 @@ trino-root io.trino - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 39963ce142b3..e0fb2530fcfe 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml @@ -540,7 +540,7 @@ **/TestTrinoGlueCatalog.java **/TestSharedGlueMetastore.java **/TestIcebergGlueCatalogAccessOperations.java - **/TestIcebergGlueCatalogMaterializedViewTest.java + **/TestIcebergGlueCatalogMaterializedView.java **/TestIcebergGlueTableOperationsInsertFailure.java **/TestIcebergGlueCatalogSkipArchive.java **/TestIcebergGcsConnectorSmokeTest.java @@ -582,7 +582,7 @@ **/TestTrinoGlueCatalog.java **/TestSharedGlueMetastore.java **/TestIcebergGlueCatalogAccessOperations.java - **/TestIcebergGlueCatalogMaterializedViewTest.java + **/TestIcebergGlueCatalogMaterializedView.java **/TestIcebergGlueTableOperationsInsertFailure.java **/TestIcebergGlueCatalogSkipArchive.java **/TestIcebergGcsConnectorSmokeTest.java 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 ce6df3c54eb2..14c01bc3b4dc 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 @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; +import com.google.common.collect.Streams; import io.airlift.json.JsonCodec; import io.airlift.log.Logger; import io.airlift.slice.Slice; @@ -126,12 +127,12 @@ import org.apache.iceberg.Transaction; import org.apache.iceberg.UpdatePartitionSpec; import org.apache.iceberg.UpdateProperties; +import org.apache.iceberg.UpdateSchema; import org.apache.iceberg.UpdateStatistics; import org.apache.iceberg.exceptions.ValidationException; import org.apache.iceberg.expressions.Expressions; import org.apache.iceberg.expressions.Term; import org.apache.iceberg.io.CloseableIterable; -import org.apache.iceberg.transforms.Transforms; import org.apache.iceberg.types.Type; import org.apache.iceberg.types.Types; import org.apache.iceberg.types.Types.IntegerType; @@ -1532,10 +1533,20 @@ public void dropColumn(ConnectorSession session, ConnectorTableHandle tableHandl IcebergColumnHandle handle = (IcebergColumnHandle) column; Table icebergTable = catalog.loadTable(session, ((IcebergTableHandle) tableHandle).getSchemaTableName()); boolean isPartitionColumn = icebergTable.spec().fields().stream() - .anyMatch(field -> field.sourceId() == handle.getId() && !isVoidTransform(field)); + .anyMatch(field -> field.sourceId() == handle.getId()); if (isPartitionColumn) { throw new TrinoException(NOT_SUPPORTED, "Cannot drop partition field: " + handle.getName()); } + int currentSpecId = icebergTable.spec().specId(); + boolean columnUsedInOlderPartitionSpecs = icebergTable.specs().entrySet().stream() + .filter(spec -> spec.getValue().specId() != currentSpecId) + .flatMap(spec -> spec.getValue().fields().stream()) + .anyMatch(field -> field.sourceId() == handle.getId()); + if (columnUsedInOlderPartitionSpecs) { + // After dropping a column which was used in older partition specs, insert/update/select fails on the table. + // So restricting user to dropping that column. https://github.com/trinodb/trino/issues/15729 + throw new TrinoException(NOT_SUPPORTED, "Cannot drop column which is used by an old partition spec: " + handle.getName()); + } try { icebergTable.updateSchema() .deleteColumn(handle.getName()) @@ -1546,11 +1557,6 @@ public void dropColumn(ConnectorSession session, ConnectorTableHandle tableHandl } } - private static boolean isVoidTransform(PartitionField field) - { - return field.transform().equals(Transforms.alwaysNull()); - } - @Override public void renameColumn(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle source, String target) { @@ -1574,16 +1580,72 @@ public void setColumnType(ConnectorSession session, ConnectorTableHandle tableHa verify(column.isBaseColumn(), "Cannot change nested field types"); Table icebergTable = catalog.loadTable(session, table.getSchemaTableName()); + Type sourceType = icebergTable.schema().findType(column.getName()); + Type newType = toIcebergType(type); try { - icebergTable.updateSchema() - .updateColumn(column.getName(), toIcebergType(type).asPrimitiveType()) - .commit(); + UpdateSchema schemaUpdate = icebergTable.updateSchema(); + buildUpdateSchema(column.getName(), sourceType, newType, schemaUpdate); + schemaUpdate.commit(); } catch (RuntimeException e) { throw new TrinoException(ICEBERG_COMMIT_ERROR, "Failed to set column type: " + firstNonNull(e.getMessage(), e), e); } } + private static void buildUpdateSchema(String name, Type sourceType, Type newType, UpdateSchema schemaUpdate) + { + if (sourceType.equals(newType)) { + return; + } + if (sourceType.isPrimitiveType() && newType.isPrimitiveType()) { + schemaUpdate.updateColumn(name, newType.asPrimitiveType()); + return; + } + if (sourceType instanceof StructType sourceRowType && newType instanceof StructType newRowType) { + // Add, update or delete fields + List fields = Streams.concat(sourceRowType.fields().stream(), newRowType.fields().stream()) + .distinct() + .collect(toImmutableList()); + for (NestedField field : fields) { + if (fieldExists(sourceRowType, field.name()) && fieldExists(newRowType, field.name())) { + buildUpdateSchema(name + "." + field.name(), sourceRowType.fieldType(field.name()), newRowType.fieldType(field.name()), schemaUpdate); + } + else if (fieldExists(newRowType, field.name())) { + schemaUpdate.addColumn(name, field.name(), field.type()); + } + else { + schemaUpdate.deleteColumn(name + "." + field.name()); + } + } + + // Order fields based on the new column type + String currentName = null; + for (NestedField field : newRowType.fields()) { + String path = name + "." + field.name(); + if (currentName == null) { + schemaUpdate.moveFirst(path); + } + else { + schemaUpdate.moveAfter(path, currentName); + } + currentName = path; + } + + return; + } + throw new IllegalArgumentException("Cannot change type from %s to %s".formatted(sourceType, newType)); + } + + private static boolean fieldExists(StructType structType, String fieldName) + { + for (NestedField field : structType.fields()) { + if (field.name().equals(fieldName)) { + return true; + } + } + return false; + } + private List getColumnMetadatas(Schema schema) { ImmutableList.Builder columns = ImmutableList.builder(); 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 8f003f45977f..2d965bc45677 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 @@ -30,6 +30,7 @@ import io.trino.plugin.iceberg.procedure.OptimizeTableProcedure; import io.trino.plugin.iceberg.procedure.RegisterTableProcedure; import io.trino.plugin.iceberg.procedure.RemoveOrphanFilesTableProcedure; +import io.trino.plugin.iceberg.procedure.UnregisterTableProcedure; import io.trino.spi.connector.ConnectorNodePartitioningProvider; import io.trino.spi.connector.ConnectorPageSinkProvider; import io.trino.spi.connector.ConnectorPageSourceProvider; @@ -83,6 +84,7 @@ public void configure(Binder binder) Multibinder procedures = newSetBinder(binder, Procedure.class); procedures.addBinding().toProvider(RollbackToSnapshotProcedure.class).in(Scopes.SINGLETON); procedures.addBinding().toProvider(RegisterTableProcedure.class).in(Scopes.SINGLETON); + procedures.addBinding().toProvider(UnregisterTableProcedure.class).in(Scopes.SINGLETON); Multibinder tableProcedures = newSetBinder(binder, TableProcedureMetadata.class); tableProcedures.addBinding().toProvider(OptimizeTableProcedure.class).in(Scopes.SINGLETON); 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 a171eceb888f..31a44eb11c03 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 @@ -75,6 +75,8 @@ Transaction newCreateTableTransaction( void registerTable(ConnectorSession session, SchemaTableName tableName, String tableLocation, String metadataLocation); + void unregisterTable(ConnectorSession session, SchemaTableName tableName); + void dropTable(ConnectorSession session, SchemaTableName schemaTableName); void renameTable(ConnectorSession session, SchemaTableName from, SchemaTableName to); 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 8bbc78b936c4..3d98a4ad2fcb 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -41,6 +41,7 @@ import io.trino.plugin.hive.TrinoViewUtil; import io.trino.plugin.hive.ViewAlreadyExistsException; import io.trino.plugin.hive.metastore.glue.GlueMetastoreStats; +import io.trino.plugin.iceberg.UnknownTableTypeException; import io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.spi.TrinoException; @@ -384,6 +385,23 @@ public void registerTable(ConnectorSession session, SchemaTableName schemaTableN createTable(schemaTableName.getSchemaName(), tableInput); } + @Override + public void unregisterTable(ConnectorSession session, SchemaTableName schemaTableName) + { + com.amazonaws.services.glue.model.Table table = getTable(session, schemaTableName) + .orElseThrow(() -> new TableNotFoundException(schemaTableName)); + if (!isIcebergTable(firstNonNull(table.getParameters(), ImmutableMap.of()))) { + throw new UnknownTableTypeException(schemaTableName); + } + + try { + deleteTable(schemaTableName.getSchemaName(), schemaTableName.getTableName()); + } + catch (AmazonServiceException e) { + throw new TrinoException(HIVE_METASTORE_ERROR, e); + } + } + @Override public void renameTable(ConnectorSession session, SchemaTableName from, SchemaTableName to) { 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 dc0df30e00b9..ca1cb44fea47 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 @@ -28,6 +28,7 @@ import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.util.HiveUtil; +import io.trino.plugin.iceberg.UnknownTableTypeException; import io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.spi.TrinoException; @@ -289,6 +290,21 @@ public void registerTable(ConnectorSession session, SchemaTableName schemaTableN metastore.createTable(builder.build(), privileges); } + @Override + public void unregisterTable(ConnectorSession session, SchemaTableName schemaTableName) + { + io.trino.plugin.hive.metastore.Table table = metastore.getTable(schemaTableName.getSchemaName(), schemaTableName.getTableName()) + .orElseThrow(() -> new TableNotFoundException(schemaTableName)); + if (!isIcebergTable(table)) { + throw new UnknownTableTypeException(schemaTableName); + } + + metastore.dropTable( + schemaTableName.getSchemaName(), + schemaTableName.getTableName(), + false /* do not delete data */); + } + @Override public List listTables(ConnectorSession session, Optional namespace) { 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 f0009c12a88c..129edefaf471 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 @@ -186,7 +186,13 @@ public Transaction newCreateTableTransaction(ConnectorSession session, SchemaTab @Override public void registerTable(ConnectorSession session, SchemaTableName tableName, String tableLocation, String metadataLocation) { - jdbcCatalog.registerTable(TableIdentifier.of(tableName.getSchemaName(), tableName.getTableName()), metadataLocation); + throw new TrinoException(NOT_SUPPORTED, "registerTable is not supported for Iceberg JDBC catalogs"); + } + + @Override + public void unregisterTable(ConnectorSession session, SchemaTableName tableName) + { + throw new TrinoException(NOT_SUPPORTED, "unregisterTable is not supported for Iceberg JDBC catalogs"); } @Override 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 e1f1a1f8e0ce..b98d26b690ec 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 @@ -211,10 +211,16 @@ public void registerTable(ConnectorSession session, SchemaTableName tableName, S throw new TrinoException(NOT_SUPPORTED, "registerTable is not supported for Iceberg REST catalog"); } + @Override + public void unregisterTable(ConnectorSession session, SchemaTableName tableName) + { + throw new TrinoException(NOT_SUPPORTED, "unregisterTable is not supported for Iceberg REST catalogs"); + } + @Override public void dropTable(ConnectorSession session, SchemaTableName schemaTableName) { - if (!restSessionCatalog.dropTable(convert(session), toIdentifier(schemaTableName))) { + if (!restSessionCatalog.purgeTable(convert(session), toIdentifier(schemaTableName))) { throw new TrinoException(ICEBERG_CATALOG_ERROR, format("Failed to drop table: %s", schemaTableName)); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/UnregisterTableProcedure.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/UnregisterTableProcedure.java new file mode 100644 index 000000000000..3feb97706abf --- /dev/null +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/UnregisterTableProcedure.java @@ -0,0 +1,99 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.iceberg.procedure; + +import com.google.common.collect.ImmutableList; +import io.trino.plugin.iceberg.catalog.TrinoCatalog; +import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; +import io.trino.spi.classloader.ThreadContextClassLoader; +import io.trino.spi.connector.ConnectorAccessControl; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.SchemaNotFoundException; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.procedure.Procedure; + +import javax.inject.Inject; +import javax.inject.Provider; + +import java.lang.invoke.MethodHandle; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static io.trino.plugin.base.util.Procedures.checkProcedureArgument; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static java.lang.invoke.MethodHandles.lookup; +import static java.util.Objects.requireNonNull; + +public class UnregisterTableProcedure + implements Provider +{ + private static final MethodHandle UNREGISTER_TABLE; + + private static final String PROCEDURE_NAME = "unregister_table"; + private static final String SYSTEM_SCHEMA = "system"; + + private static final String SCHEMA_NAME = "SCHEMA_NAME"; + private static final String TABLE_NAME = "TABLE_NAME"; + + static { + try { + UNREGISTER_TABLE = lookup().unreflect(UnregisterTableProcedure.class.getMethod("unregisterTable", ConnectorAccessControl.class, ConnectorSession.class, String.class, String.class)); + } + catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + + private final TrinoCatalogFactory catalogFactory; + + @Inject + public UnregisterTableProcedure(TrinoCatalogFactory catalogFactory) + { + this.catalogFactory = requireNonNull(catalogFactory, "catalogFactory is null"); + } + + @Override + public Procedure get() + { + return new Procedure( + SYSTEM_SCHEMA, + PROCEDURE_NAME, + ImmutableList.of( + new Procedure.Argument(SCHEMA_NAME, VARCHAR), + new Procedure.Argument(TABLE_NAME, VARCHAR)), + UNREGISTER_TABLE.bindTo(this)); + } + + public void unregisterTable(ConnectorAccessControl accessControl, ConnectorSession session, String schemaName, String tableName) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(getClass().getClassLoader())) { + doUnregisterTable(accessControl, session, schemaName, tableName); + } + } + + private void doUnregisterTable(ConnectorAccessControl accessControl, ConnectorSession session, String schemaName, String tableName) + { + checkProcedureArgument(!isNullOrEmpty(schemaName), "schema_name cannot be null or empty"); + checkProcedureArgument(!isNullOrEmpty(tableName), "table_name cannot be null or empty"); + SchemaTableName schemaTableName = new SchemaTableName(schemaName, tableName); + + accessControl.checkCanDropTable(null, schemaTableName); + + TrinoCatalog catalog = catalogFactory.create(session.getIdentity()); + if (!catalog.namespaceExists(session, schemaTableName.getSchemaName())) { + throw new SchemaNotFoundException(schemaName); + } + + catalog.unregisterTable(session, schemaTableName); + } +} 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 01f5ed24ffcc..447e2706bbaf 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 @@ -28,6 +28,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.concurrent.MoreFutures.getFutureValue; +import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.DROP_TABLE; +import static io.trino.testing.TestingAccessControlManager.privilege; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -303,6 +305,96 @@ public void testRegisterTableWithMetadataFile() assertUpdate(format("DROP TABLE %s", tableName)); } + @Test + public void testUnregisterTable() + { + String tableName = "test_unregister_table_" + randomNameSuffix(); + + assertUpdate("CREATE TABLE " + tableName + " AS SELECT 1 a", 1); + String tableLocation = getTableLocation(tableName); + + assertUpdate("CALL system.unregister_table(CURRENT_SCHEMA, '" + tableName + "')"); + assertQueryFails("SELECT * FROM " + tableName, ".* Table .* does not exist"); + + assertUpdate("CALL iceberg.system.register_table(CURRENT_SCHEMA, '" + tableName + "', '" + tableLocation + "')"); + assertQuery("SELECT * FROM " + tableName, "VALUES 1"); + + assertUpdate("DROP TABLE " + tableName); + } + + @Test + public void testUnregisterBrokenTable() + { + String tableName = "test_unregister_broken_table_" + randomNameSuffix(); + + assertUpdate("CREATE TABLE " + tableName + " AS SELECT 1 a", 1); + String tableLocation = getTableLocation(tableName); + + // Break the table by deleting files from the storage + deleteDirectory(tableLocation); + + // Verify unregister_table successfully deletes the table from metastore + assertUpdate("CALL system.unregister_table(CURRENT_SCHEMA, '" + tableName + "')"); + assertQueryFails("SELECT * FROM " + tableName, ".* Table .* does not exist"); + } + + protected abstract void deleteDirectory(String location); + + @Test + public void testUnregisterTableNotExistingSchema() + { + String schemaName = "test_unregister_table_not_existing_schema_" + randomNameSuffix(); + assertQueryFails( + "CALL system.unregister_table('" + schemaName + "', 'non_existent_table')", + "Schema " + schemaName + " not found"); + } + + @Test + public void testUnregisterTableNotExistingTable() + { + String tableName = "test_unregister_table_not_existing_table_" + randomNameSuffix(); + assertQueryFails( + "CALL system.unregister_table(CURRENT_SCHEMA, '" + tableName + "')", + "Table .* not found"); + } + + @Test + public void testRepeatUnregisterTable() + { + String tableName = "test_repeat_unregister_table_not_" + randomNameSuffix(); + assertQueryFails( + "CALL system.unregister_table(CURRENT_SCHEMA, '" + tableName + "')", + "Table .* not found"); + + assertUpdate("CREATE TABLE " + tableName + " AS SELECT 1 a", 1); + String tableLocation = getTableLocation(tableName); + + assertUpdate("CALL system.unregister_table(CURRENT_SCHEMA, '" + tableName + "')"); + + // Verify failure the procedure can't unregister the tables more than once + assertQueryFails("CALL system.unregister_table(CURRENT_SCHEMA, '" + tableName + "')", "Table .* not found"); + + assertUpdate("CALL iceberg.system.register_table(CURRENT_SCHEMA, '" + tableName + "', '" + tableLocation + "')"); + assertQuery("SELECT * FROM " + tableName, "VALUES 1"); + + assertUpdate("DROP TABLE " + tableName); + } + + @Test + public void testUnregisterTableAccessControl() + { + String tableName = "test_unregister_table_access_control_" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " AS SELECT 1 a", 1); + + assertAccessDenied( + "CALL system.unregister_table(CURRENT_SCHEMA, '" + tableName + "')", + "Cannot drop table .*", + privilege(tableName, DROP_TABLE)); + + assertQuery("SELECT * FROM " + tableName, "VALUES 1"); + assertUpdate("DROP TABLE " + tableName); + } + private String getTableLocation(String tableName) { return (String) computeScalar("SELECT DISTINCT regexp_replace(\"$path\", '/[^/]*/[^/]*$', '') FROM " + 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 e04ebc684094..b0b73d531784 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 @@ -1215,7 +1215,19 @@ public void testDropPartitionColumn() assertUpdate("CREATE TABLE " + tableName + " (id INTEGER, name VARCHAR, age INTEGER) WITH (partitioning = ARRAY['id', 'truncate(name, 5)', 'void(age)'])"); assertQueryFails("ALTER TABLE " + tableName + " DROP COLUMN id", "Cannot drop partition field: id"); assertQueryFails("ALTER TABLE " + tableName + " DROP COLUMN name", "Cannot drop partition field: name"); - assertUpdate("ALTER TABLE " + tableName + " DROP COLUMN age"); + assertQueryFails("ALTER TABLE " + tableName + " DROP COLUMN age", "Cannot drop partition field: age"); + dropTable(tableName); + } + + @Test + public void testDropColumnUsedInOlderPartitionSpecs() + { + String tableName = "test_drop_partition_column_" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " (id INTEGER, name VARCHAR, age INTEGER) WITH (partitioning = ARRAY['id', 'truncate(name, 5)', 'void(age)'])"); + assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES partitioning = ARRAY[]"); + assertQueryFails("ALTER TABLE " + tableName + " DROP COLUMN id", "Cannot drop column which is used by an old partition spec: id"); + assertQueryFails("ALTER TABLE " + tableName + " DROP COLUMN name", "Cannot drop column which is used by an old partition spec: name"); + assertQueryFails("ALTER TABLE " + tableName + " DROP COLUMN age", "Cannot drop column which is used by an old partition spec: age"); dropTable(tableName); } @@ -5033,6 +5045,21 @@ public void testDroppingIcebergAndCreatingANewTableWithTheSameNameShouldBePossib dropTable("test_iceberg_recreate"); } + @Test + public void testDropTableDeleteData() + { + String tableName = "test_drop_table_delete_data" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " (a_int) AS VALUES (1)", 1); + String tableLocation = getTableLocation(tableName); + assertUpdate("DROP TABLE " + tableName); + + // Create a new table with the same location to verify the data was deleted in the above DROP TABLE + assertUpdate("CREATE TABLE " + tableName + "(a_int INTEGER) WITH (location = '" + tableLocation + "')"); + assertQueryReturnsEmptyResult("SELECT * FROM " + tableName); + + assertUpdate("DROP TABLE " + tableName); + } + @Test public void testPathHiddenColumn() { @@ -6349,7 +6376,6 @@ protected Optional filterSetColumnTypesDataProvider(SetColum case "decimal(5,3) -> decimal(5,2)": case "varchar -> char(20)": case "array(integer) -> array(bigint)": - case "row(x integer) -> row(x bigint)": // Iceberg allows updating column types if the update is safe. Safe updates are: // - int to bigint // - float to double @@ -6367,7 +6393,7 @@ protected Optional filterSetColumnTypesDataProvider(SetColum @Override protected void verifySetColumnTypeFailurePermissible(Throwable e) { - assertThat(e).hasMessageMatching(".*(Cannot change column type|not supported for Iceberg|Not a primitive type).*"); + assertThat(e).hasMessageMatching(".*(Cannot change column type|not supported for Iceberg|Not a primitive type|Cannot change type ).*"); } private Session prepareCleanUpSession() diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java index 8a444e901b17..2351832a1e10 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java @@ -20,7 +20,6 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.sql.tree.ExplainType; import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; import io.trino.transaction.TransactionId; import io.trino.transaction.TransactionManager; @@ -53,8 +52,6 @@ public abstract class BaseIcebergMaterializedViewTest { protected final String storageSchemaName = "testing_storage_schema_" + randomNameSuffix(); - protected abstract String getSchemaName(); - protected abstract String getSchemaDirectory(); @BeforeClass @@ -73,8 +70,9 @@ public void setUp() @Test public void testShowTables() { + String schema = getSession().getSchema().orElseThrow(); assertUpdate("CREATE MATERIALIZED VIEW materialized_view_show_tables_test AS SELECT * FROM base_table1"); - SchemaTableName storageTableName = getStorageTable("iceberg", "materialized_view_show_tables_test"); + SchemaTableName storageTableName = getStorageTable("iceberg", schema, "materialized_view_show_tables_test"); Set expectedTables = ImmutableSet.of("base_table1", "base_table2", "materialized_view_show_tables_test", storageTableName.getTableName()); Set actualTables = computeActual("SHOW TABLES").getOnlyColumnAsSet().stream() @@ -108,7 +106,7 @@ public void testMaterializedViewsMetadata() "VALUES ('%s', '%s', '%s')", catalogName, schemaName, - getStorageTable(catalogName, materializedViewName))); + getStorageTable(catalogName, schemaName, materializedViewName))); // test freshness update assertQuery( @@ -154,6 +152,8 @@ public void testCreateWithDuplicateSourceTableSucceeds() @Test public void testShowCreate() { + String schema = getSession().getSchema().orElseThrow(); + assertUpdate("CREATE MATERIALIZED VIEW materialized_view_with_property " + "WITH (\n" + " partitioning = ARRAY['_date'],\n" + @@ -164,7 +164,7 @@ public void testShowCreate() assertThat((String) computeScalar("SHOW CREATE MATERIALIZED VIEW materialized_view_with_property")) .matches( - "\\QCREATE MATERIALIZED VIEW iceberg." + getSchemaName() + ".materialized_view_with_property\n" + + "\\QCREATE MATERIALIZED VIEW iceberg." + schema + ".materialized_view_with_property\n" + "WITH (\n" + " format = 'ORC',\n" + " format_version = 2,\n" + @@ -172,7 +172,7 @@ public void testShowCreate() " orc_bloom_filter_columns = ARRAY['_date'],\n" + " orc_bloom_filter_fpp = 1E-1,\n" + " partitioning = ARRAY['_date'],\n" + - " storage_schema = '" + getSchemaName() + "'\n" + + " storage_schema = '" + schema + "'\n" + ") AS\n" + "SELECT\n" + " _bigint\n" + @@ -192,11 +192,12 @@ public void testSystemMaterializedViewProperties() @Test public void testSessionCatalogSchema() { + String schema = getSession().getSchema().orElseThrow(); Session session = Session.builder(getSession()) .setCatalog("tpch") .setSchema("tiny") .build(); - String qualifiedMaterializedViewName = "iceberg." + getSchemaName() + ".materialized_view_session_test"; + String qualifiedMaterializedViewName = "iceberg." + schema + ".materialized_view_session_test"; assertUpdate(session, "CREATE MATERIALIZED VIEW " + qualifiedMaterializedViewName + " AS SELECT * FROM nation"); assertQuery(session, "SELECT COUNT(*) FROM " + qualifiedMaterializedViewName, "VALUES 25"); assertUpdate(session, "DROP MATERIALIZED VIEW " + qualifiedMaterializedViewName); @@ -205,7 +206,7 @@ public void testSessionCatalogSchema() .setCatalog(Optional.empty()) .setSchema(Optional.empty()) .build(); - assertUpdate(session, "CREATE MATERIALIZED VIEW " + qualifiedMaterializedViewName + " AS SELECT * FROM iceberg." + getSchemaName() + ".base_table1"); + assertUpdate(session, "CREATE MATERIALIZED VIEW " + qualifiedMaterializedViewName + " AS SELECT * FROM iceberg." + schema + ".base_table1"); assertQuery(session, "SELECT COUNT(*) FROM " + qualifiedMaterializedViewName, "VALUES 6"); assertUpdate(session, "DROP MATERIALIZED VIEW " + qualifiedMaterializedViewName); } @@ -213,9 +214,10 @@ public void testSessionCatalogSchema() @Test public void testDropIfExists() { + String schema = getSession().getSchema().orElseThrow(); assertQueryFails( "DROP MATERIALIZED VIEW non_existing_materialized_view", - "line 1:1: Materialized view 'iceberg." + getSchemaName() + ".non_existing_materialized_view' does not exist"); + "line 1:1: Materialized view 'iceberg." + schema + ".non_existing_materialized_view' does not exist"); assertUpdate("DROP MATERIALIZED VIEW IF EXISTS non_existing_materialized_view"); } @@ -256,8 +258,9 @@ public void testRefreshDenyPermission() @Test public void testRefreshAllowedWithRestrictedStorageTable() { + String schema = getSession().getSchema().orElseThrow(); assertUpdate("CREATE MATERIALIZED VIEW materialized_view_refresh AS SELECT * FROM base_table1"); - SchemaTableName storageTable = getStorageTable("iceberg", "materialized_view_refresh"); + SchemaTableName storageTable = getStorageTable("iceberg", schema, "materialized_view_refresh"); assertAccessAllowed( "REFRESH MATERIALIZED VIEW materialized_view_refresh", @@ -294,58 +297,42 @@ public void testCreateRefreshSelect() // 4. Select the data from refreshed materialized view, verify the number of rows in the result // 5. Ensure that the plan uses the storage table // 6. In some cases validate the result data - MaterializedResult baseResult = computeActual("SELECT * from materialized_view_no_part"); - assertEquals(baseResult.getRowCount(), 6); - String plan = getExplainPlan("SELECT * from materialized_view_no_part", ExplainType.Type.IO); - assertThat(plan).contains("base_table1"); + assertEquals(computeActual("SELECT * FROM materialized_view_no_part").getRowCount(), 6); + assertThat(getExplainPlan("SELECT * FROM materialized_view_no_part", ExplainType.Type.IO)) + .contains("base_table1"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_no_part", 6); - MaterializedResult viewResult = computeActual("SELECT * from materialized_view_no_part"); - assertEquals(viewResult.getRowCount(), 6); - plan = getExplainPlan("SELECT * from materialized_view_no_part", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table1"); - - baseResult = computeActual("SELECT * from materialized_view_agg"); - assertEquals(baseResult.getRowCount(), 3); - plan = getExplainPlan("SELECT * from materialized_view_agg", ExplainType.Type.IO); - assertThat(plan).contains("base_table1"); + assertEquals(computeActual("SELECT * FROM materialized_view_no_part").getRowCount(), 6); + assertThat(getExplainPlan("SELECT * FROM materialized_view_no_part", ExplainType.Type.IO)).doesNotContain("base_table1"); + + assertEquals(computeActual("SELECT * FROM materialized_view_agg").getRowCount(), 3); + assertThat(getExplainPlan("SELECT * FROM materialized_view_agg", ExplainType.Type.IO)) + .contains("base_table1"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_agg", 3); - viewResult = computeActual("SELECT * from materialized_view_agg"); - assertEquals(viewResult.getRowCount(), 3); - plan = getExplainPlan("SELECT * from materialized_view_agg", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table1"); - assertQuery(session, "SELECT * from materialized_view_agg", "VALUES (DATE '2019-09-10', 2)," + + assertEquals(computeActual("SELECT * FROM materialized_view_agg").getRowCount(), 3); + assertThat(getExplainPlan("SELECT * FROM materialized_view_agg", ExplainType.Type.IO)) + .doesNotContain("base_table1"); + assertQuery(session, "SELECT * FROM materialized_view_agg", "VALUES (DATE '2019-09-10', 2)," + "(DATE '2019-09-08', 1), (DATE '2019-09-09', 3)"); - baseResult = computeActual("SELECT * from materialized_view_part"); - assertEquals(baseResult.getRowCount(), 3); - plan = getExplainPlan("SELECT * from materialized_view_part", ExplainType.Type.IO); - assertThat(plan).contains("base_table1"); + assertEquals(computeActual("SELECT * FROM materialized_view_part").getRowCount(), 3); + assertThat(getExplainPlan("SELECT * FROM materialized_view_part", ExplainType.Type.IO)) + .contains("base_table1"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_part", 3); - viewResult = computeActual("SELECT * from materialized_view_part"); - assertEquals(viewResult.getRowCount(), 3); - plan = getExplainPlan("SELECT * from materialized_view_part", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table1"); - - baseResult = computeActual("SELECT * from materialized_view_join"); - assertEquals(baseResult.getRowCount(), 5); - plan = getExplainPlan("SELECT * from materialized_view_join", ExplainType.Type.IO); - assertThat(plan).contains("base_table1", "base_table2"); + assertEquals(computeActual("SELECT * FROM materialized_view_part").getRowCount(), 3); + assertThat(getExplainPlan("SELECT * FROM materialized_view_part", ExplainType.Type.IO)).doesNotContain("base_table1"); + + assertEquals(computeActual("SELECT * FROM materialized_view_join").getRowCount(), 5); + assertThat(getExplainPlan("SELECT * FROM materialized_view_join", ExplainType.Type.IO)).contains("base_table1", "base_table2"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_join", 5); - viewResult = computeActual("SELECT * from materialized_view_join"); - assertEquals(viewResult.getRowCount(), 5); - plan = getExplainPlan("SELECT * from materialized_view_join", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table1", "base_table2"); - - baseResult = computeActual("SELECT * from materialized_view_join_part"); - assertEquals(baseResult.getRowCount(), 4); - plan = getExplainPlan("SELECT * from materialized_view_join_part", ExplainType.Type.IO); - assertThat(plan).contains("base_table1", "base_table2"); + assertEquals(computeActual("SELECT * FROM materialized_view_join").getRowCount(), 5); + assertThat(getExplainPlan("SELECT * FROM materialized_view_join", ExplainType.Type.IO)).doesNotContain("base_table1", "base_table2"); + + assertEquals(computeActual("SELECT * FROM materialized_view_join_part").getRowCount(), 4); + assertThat(getExplainPlan("SELECT * FROM materialized_view_join_part", ExplainType.Type.IO)).contains("base_table1", "base_table2"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_join_part", 4); - viewResult = computeActual("SELECT * from materialized_view_join_part"); - assertEquals(viewResult.getRowCount(), 4); - plan = getExplainPlan("SELECT * from materialized_view_join_part", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table1", "base_table2"); - assertQuery(session, "SELECT * from materialized_view_join_part", "VALUES (2, 'a', DATE '2019-09-09', 1), " + + assertEquals(computeActual("SELECT * FROM materialized_view_join_part").getRowCount(), 4); + assertThat(getExplainPlan("SELECT * FROM materialized_view_join_part", ExplainType.Type.IO)).doesNotContain("base_table1", "base_table2"); + assertQuery(session, "SELECT * FROM materialized_view_join_part", "VALUES (2, 'a', DATE '2019-09-09', 1), " + "(0, 'a', DATE '2019-09-08', 2), (3, 'a', DATE '2019-09-09', 1), (1, 'a', DATE '2019-09-09', 1)"); assertUpdate("DROP MATERIALIZED VIEW materialized_view_no_part"); @@ -366,13 +353,13 @@ public void testDetectStaleness() assertUpdate("INSERT INTO base_table4 VALUES ('a', 0, DATE '2019-09-08'), ('a', 1, DATE '2019-09-08'), ('a', 0, DATE '2019-09-09')", 3); // A partitioned materialized view with grouping and aggregation - assertUpdate("CREATE MATERIALIZED VIEW materialized_view_part_stale WITH (partitioning = ARRAY['_date']) as select _date, count(_date) as num_dates from base_table3 group by 1"); + assertUpdate("CREATE MATERIALIZED VIEW materialized_view_part_stale WITH (partitioning = ARRAY['_date']) AS SELECT _date, count(_date) AS num_dates FROM base_table3 GROUP BY 1"); // A non-partitioned join materialized view assertUpdate("CREATE MATERIALIZED VIEW materialized_view_join_stale as " + - "select t2._bigint, _varchar, t1._date from base_table3 t1, base_table4 t2 where t1._date = t2._date"); + "SELECT t2._bigint, _varchar, t1._date FROM base_table3 t1, base_table4 t2 WHERE t1._date = t2._date"); // A partitioned join materialized view assertUpdate("CREATE MATERIALIZED VIEW materialized_view_join_part_stale WITH (partitioning = ARRAY['_date', '_bigint']) as " + - "select t1._bigint, _varchar, t2._date, sum(1) as my_sum from base_table3 t1, base_table4 t2 where t1._date = t2._date group by 1, 2, 3 order by 1, 2"); + "SELECT t1._bigint, _varchar, t2._date, sum(1) AS my_sum FROM base_table3 t1, base_table4 t2 WHERE t1._date = t2._date GROUP BY 1, 2, 3 ORDER BY 1, 2"); // Ensure that when data is inserted into base table, materialized view is rendered stale. Note that, currently updates and deletes to/from iceberg tables is not supported. assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_part_stale", 2); @@ -380,29 +367,29 @@ public void testDetectStaleness() assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_join_part_stale", 3); assertUpdate("INSERT INTO base_table3 VALUES (3, DATE '2019-09-09'), (4, DATE '2019-09-10'), (5, DATE '2019-09-10')", 3); - String plan = getExplainPlan("SELECT * from materialized_view_part_stale", ExplainType.Type.IO); - assertThat(plan).contains("base_table3"); + assertThat(getExplainPlan("SELECT * FROM materialized_view_part_stale", ExplainType.Type.IO)) + .contains("base_table3"); - plan = getExplainPlan("SELECT * from materialized_view_join_stale", ExplainType.Type.IO); Condition containsTable3 = new Condition<>(p -> p.contains("base_table3"), "base_table3"); Condition containsTable4 = new Condition<>(p -> p.contains("base_table4"), "base_table4"); - assertThat(plan).is(anyOf(containsTable3, containsTable4)); + assertThat(getExplainPlan("SELECT * FROM materialized_view_join_stale", ExplainType.Type.IO)) + .is(anyOf(containsTable3, containsTable4)); - plan = getExplainPlan("SELECT * from materialized_view_join_part_stale", ExplainType.Type.IO); - assertThat(plan).is(anyOf(containsTable3, containsTable4)); + assertThat(getExplainPlan("SELECT * FROM materialized_view_join_part_stale", ExplainType.Type.IO)) + .is(anyOf(containsTable3, containsTable4)); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_part_stale", 3); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_join_stale", 5); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_join_part_stale", 4); - plan = getExplainPlan("SELECT * from materialized_view_part_stale", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table3"); + assertThat(getExplainPlan("SELECT * FROM materialized_view_part_stale", ExplainType.Type.IO)) + .doesNotContain("base_table3"); - plan = getExplainPlan("SELECT * from materialized_view_join_stale", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table3", "base_table4"); + assertThat(getExplainPlan("SELECT * FROM materialized_view_join_stale", ExplainType.Type.IO)) + .doesNotContain("base_table3", "base_table4"); - plan = getExplainPlan("SELECT * from materialized_view_join_part_stale", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table3", "base_table4"); + assertThat(getExplainPlan("SELECT * FROM materialized_view_join_part_stale", ExplainType.Type.IO)) + .doesNotContain("base_table3", "base_table4"); assertUpdate("DROP TABLE IF EXISTS base_table3"); assertUpdate("DROP TABLE IF EXISTS base_table4"); @@ -414,38 +401,40 @@ public void testDetectStaleness() @Test public void testSqlFeatures() { + String schema = getSession().getSchema().orElseThrow(); + // Materialized views to test SQL features - assertUpdate("CREATE MATERIALIZED VIEW materialized_view_window WITH (partitioning = ARRAY['_date']) as select _date, " + - "sum(_bigint) OVER (partition by _date order by _date) as sum_ints from base_table1"); + assertUpdate("CREATE MATERIALIZED VIEW materialized_view_window WITH (partitioning = ARRAY['_date']) AS SELECT _date, " + + "sum(_bigint) OVER (PARTITION BY _date ORDER BY _date) as sum_ints from base_table1"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_window", 6); - assertUpdate("CREATE MATERIALIZED VIEW materialized_view_union WITH (partitioning = ARRAY['_date']) as " + - "select _date, count(_date) as num_dates from base_table1 group by 1 union " + + assertUpdate("CREATE MATERIALIZED VIEW materialized_view_union WITH (partitioning = ARRAY['_date']) AS " + + "select _date, count(_date) as num_dates from base_table1 group by 1 UNION " + "select _date, count(_date) as num_dates from base_table2 group by 1"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_union", 5); - assertUpdate("CREATE MATERIALIZED VIEW materialized_view_subquery WITH (partitioning = ARRAY['_date']) as " + - "select _date, count(_date) as num_dates from base_table1 where _date = (select max(_date) from base_table2) group by 1"); + assertUpdate("CREATE MATERIALIZED VIEW materialized_view_subquery WITH (partitioning = ARRAY['_date']) AS " + + "SELECT _date, count(_date) AS num_dates FROM base_table1 WHERE _date = (select max(_date) FROM base_table2) GROUP BY 1"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_subquery", 1); // This set of tests intend to test various SQL features in the context of materialized views. It also tests commands pertaining to materialized views. - String plan = getExplainPlan("SELECT * from materialized_view_window", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table1"); - plan = getExplainPlan("SELECT * from materialized_view_union", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table1"); - plan = getExplainPlan("SELECT * from materialized_view_subquery", ExplainType.Type.IO); - assertThat(plan).doesNotContain("base_table1"); - - String qualifiedMaterializedViewName = "iceberg." + getSchemaName() + ".materialized_view_window"; - assertQueryFails("show create view materialized_view_window", + assertThat(getExplainPlan("SELECT * FROM materialized_view_window", ExplainType.Type.IO)) + .doesNotContain("base_table1"); + assertThat(getExplainPlan("SELECT * FROM materialized_view_union", ExplainType.Type.IO)) + .doesNotContain("base_table1"); + assertThat(getExplainPlan("SELECT * FROM materialized_view_subquery", ExplainType.Type.IO)) + .doesNotContain("base_table1"); + + String qualifiedMaterializedViewName = "iceberg." + schema + ".materialized_view_window"; + assertQueryFails("SHOW CREATE VIEW materialized_view_window", "line 1:1: Relation '" + qualifiedMaterializedViewName + "' is a materialized view, not a view"); - assertThat((String) computeScalar("show create materialized view materialized_view_window")) + assertThat((String) computeScalar("SHOW CREATE MATERIALIZED VIEW materialized_view_window")) .matches("\\QCREATE MATERIALIZED VIEW " + qualifiedMaterializedViewName + "\n" + "WITH (\n" + " format = 'ORC',\n" + " format_version = 2,\n" + " location = '" + getSchemaDirectory() + "/st_\\E[0-9a-f]+-[0-9a-f]+\\Q',\n" + " partitioning = ARRAY['_date'],\n" + - " storage_schema = '" + getSchemaName() + "'\n" + + " storage_schema = '" + schema + "'\n" + ") AS\n" + "SELECT\n" + " _date\n" + @@ -456,16 +445,11 @@ public void testSqlFeatures() assertQueryFails("INSERT INTO materialized_view_window VALUES (0, '2019-09-08'), (1, DATE '2019-09-09'), (2, DATE '2019-09-09')", "Inserting into materialized views is not supported"); - MaterializedResult result = computeActual("explain (type logical) refresh materialized view materialized_view_window"); - assertEquals(result.getRowCount(), 1); - result = computeActual("explain (type distributed) refresh materialized view materialized_view_window"); - assertEquals(result.getRowCount(), 1); - result = computeActual("explain (type validate) refresh materialized view materialized_view_window"); - assertEquals(result.getRowCount(), 1); - result = computeActual("explain (type io) refresh materialized view materialized_view_window"); - assertEquals(result.getRowCount(), 1); - result = computeActual("explain analyze refresh materialized view materialized_view_window"); - assertEquals(result.getRowCount(), 1); + computeScalar("EXPLAIN (TYPE LOGICAL) REFRESH MATERIALIZED VIEW materialized_view_window"); + computeScalar("EXPLAIN (TYPE DISTRIBUTED) REFRESH MATERIALIZED VIEW materialized_view_window"); + computeScalar("EXPLAIN (TYPE VALIDATE) REFRESH MATERIALIZED VIEW materialized_view_window"); + computeScalar("EXPLAIN (TYPE IO) REFRESH MATERIALIZED VIEW materialized_view_window"); + computeScalar("EXPLAIN ANALYZE REFRESH MATERIALIZED VIEW materialized_view_window"); assertUpdate("DROP MATERIALIZED VIEW materialized_view_window"); assertUpdate("DROP MATERIALIZED VIEW materialized_view_union"); @@ -480,11 +464,12 @@ public void testReplace() assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_replace", 3); assertUpdate("CREATE OR REPLACE MATERIALIZED VIEW materialized_view_replace as select sum(1) as num_rows from base_table2"); - String plan = getExplainPlan("SELECT * from materialized_view_replace", ExplainType.Type.IO); - assertThat(plan).contains("base_table2"); + assertThat(getExplainPlan("SELECT * FROM materialized_view_replace", ExplainType.Type.IO)) + .contains("base_table2"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_replace", 1); - MaterializedResult viewResult = computeActual("SELECT * from materialized_view_replace"); - assertEquals(viewResult.getRowCount(), 1); + computeScalar("SELECT * FROM materialized_view_replace"); + assertThat(query("SELECT * FROM materialized_view_replace")) + .matches("VALUES BIGINT '3'"); assertUpdate("DROP MATERIALIZED VIEW materialized_view_replace"); } @@ -492,29 +477,32 @@ public void testReplace() @Test public void testCreateMaterializedViewWhenTableExists() { + String schema = getSession().getSchema().orElseThrow(); assertUpdate("CREATE TABLE test_create_materialized_view_when_table_exists (a INT, b INT)"); assertThatThrownBy(() -> query("CREATE OR REPLACE MATERIALIZED VIEW test_create_materialized_view_when_table_exists AS SELECT sum(1) AS num_rows FROM base_table2")) - .hasMessage("Existing table is not a Materialized View: " + getSchemaName() + ".test_create_materialized_view_when_table_exists"); + .hasMessage("Existing table is not a Materialized View: " + schema + ".test_create_materialized_view_when_table_exists"); assertThatThrownBy(() -> query("CREATE MATERIALIZED VIEW IF NOT EXISTS test_create_materialized_view_when_table_exists AS SELECT sum(1) AS num_rows FROM base_table2")) - .hasMessage("Existing table is not a Materialized View: " + getSchemaName() + ".test_create_materialized_view_when_table_exists"); + .hasMessage("Existing table is not a Materialized View: " + schema + ".test_create_materialized_view_when_table_exists"); assertUpdate("DROP TABLE test_create_materialized_view_when_table_exists"); } @Test public void testDropMaterializedViewCannotDropTable() { + String schema = getSession().getSchema().orElseThrow(); assertUpdate("CREATE TABLE test_drop_materialized_view_cannot_drop_table (a INT, b INT)"); assertThatThrownBy(() -> query("DROP MATERIALIZED VIEW test_drop_materialized_view_cannot_drop_table")) - .hasMessageContaining("Materialized view 'iceberg." + getSchemaName() + ".test_drop_materialized_view_cannot_drop_table' does not exist, but a table with that name exists"); + .hasMessageContaining("Materialized view 'iceberg." + schema + ".test_drop_materialized_view_cannot_drop_table' does not exist, but a table with that name exists"); assertUpdate("DROP TABLE test_drop_materialized_view_cannot_drop_table"); } @Test public void testRenameMaterializedViewCannotRenameTable() { + String schema = getSession().getSchema().orElseThrow(); assertUpdate("CREATE TABLE test_rename_materialized_view_cannot_rename_table (a INT, b INT)"); assertThatThrownBy(() -> query("ALTER MATERIALIZED VIEW test_rename_materialized_view_cannot_rename_table RENAME TO new_materialized_view_name")) - .hasMessageContaining("Materialized View 'iceberg." + getSchemaName() + ".test_rename_materialized_view_cannot_rename_table' does not exist, but a table with that name exists"); + .hasMessageContaining("Materialized View 'iceberg." + schema + ".test_rename_materialized_view_cannot_rename_table' does not exist, but a table with that name exists"); assertUpdate("DROP TABLE test_rename_materialized_view_cannot_rename_table"); } @@ -528,24 +516,24 @@ public void testNestedMaterializedViews() assertUpdate("CREATE MATERIALIZED VIEW materialized_view_level2 WITH (partitioning = ARRAY['_date']) as select _date, num_dates from materialized_view_level1"); // Unrefreshed 2nd level materialized view .. resolves to base table - String plan = getExplainPlan("select * from materialized_view_level2", ExplainType.Type.IO); - assertThat(plan).contains("base_table5"); - assertUpdate("refresh materialized view materialized_view_level2", 2); - plan = getExplainPlan("select * from materialized_view_level2", ExplainType.Type.IO); + assertThat(getExplainPlan("SELECT * FROM materialized_view_level2", ExplainType.Type.IO)) + .contains("base_table5"); + assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_level2", 2); // Refreshed 2nd level materialized view .. resolves to storage table - assertThat(plan).doesNotContain("base_table5"); + assertThat(getExplainPlan("SELECT * FROM materialized_view_level2", ExplainType.Type.IO)) + .doesNotContain("base_table5"); // Re-refreshing 2nd level materialized view is a no-op - assertUpdate("refresh materialized view materialized_view_level2", 0); + assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_level2", 0); // Insert into the base table assertUpdate("INSERT INTO base_table5 VALUES (3, DATE '2019-09-09'), (4, DATE '2019-09-10'), (5, DATE '2019-09-10')", 3); - assertUpdate("refresh materialized view materialized_view_level2", 3); + assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_level2", 3); // Refreshing the 2nd level (outer-most) materialized view does not refresh the 1st level (inner) materialized view. - plan = getExplainPlan("select * from materialized_view_level1", ExplainType.Type.IO); - assertThat(plan).contains("base_table5"); + assertThat(getExplainPlan("SELECT * FROM materialized_view_level1", ExplainType.Type.IO)) + .contains("base_table5"); assertUpdate("DROP TABLE IF EXISTS base_table5"); assertUpdate("DROP MATERIALIZED VIEW materialized_view_level1"); @@ -556,13 +544,14 @@ public void testNestedMaterializedViews() public void testStorageSchemaProperty() { String catalogName = getSession().getCatalog().orElseThrow(); + String schemaName = getSession().getSchema().orElseThrow(); String viewName = "storage_schema_property_test"; assertUpdate("CREATE SCHEMA IF NOT EXISTS " + catalogName + "." + storageSchemaName); assertUpdate( "CREATE MATERIALIZED VIEW " + viewName + " " + "WITH (storage_schema = '" + storageSchemaName + "') AS " + "SELECT * FROM base_table1"); - SchemaTableName storageTable = getStorageTable(catalogName, viewName); + SchemaTableName storageTable = getStorageTable(catalogName, schemaName, viewName); assertThat(storageTable.getSchemaName()).isEqualTo(storageSchemaName); assertUpdate("REFRESH MATERIALIZED VIEW " + viewName, 6); @@ -591,16 +580,16 @@ public void testStorageSchemaProperty() "SELECT * FROM base_table1")) .hasMessageContaining("non_existent not found"); assertThatThrownBy(() -> query("DESCRIBE " + viewName)) - .hasMessageContaining(format("'iceberg.%s.%s' does not exist", getSchemaName(), viewName)); + .hasMessageContaining(format("'iceberg.%s.%s' does not exist", schemaName, viewName)); } - private SchemaTableName getStorageTable(String catalogName, String objectName) + private SchemaTableName getStorageTable(String catalogName, String schemaName, String objectName) { TransactionManager transactionManager = getQueryRunner().getTransactionManager(); TransactionId transactionId = transactionManager.beginTransaction(false); Session session = getSession().beginTransactionId(transactionId, transactionManager, getQueryRunner().getAccessControl()); Optional materializedView = getQueryRunner().getMetadata() - .getMaterializedView(session, new QualifiedObjectName(catalogName, getSchemaName(), objectName)); + .getMaterializedView(session, new QualifiedObjectName(catalogName, schemaName, objectName)); assertThat(materializedView).isPresent(); return materializedView.get().getStorageTable().get().getSchemaTableName(); } 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 94794825398a..d2a1d432edf7 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 @@ -20,6 +20,7 @@ import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; import io.trino.testing.QueryRunner; +import io.trino.testing.minio.MinioClient; import org.apache.iceberg.FileFormat; import org.intellij.lang.annotations.Language; import org.testng.annotations.Test; @@ -233,4 +234,17 @@ protected String getMetadataLocation(String tableName) .getTable(schemaName, tableName).orElseThrow() .getParameters().get("metadata_location"); } + + @Override + protected void deleteDirectory(String location) + { + String prefix = "s3://" + bucketName + "/"; + String key = location.substring(prefix.length()); + + MinioClient minio = hiveMinioDataLake.getMinioClient(); + for (String file : minio.listObjects(bucketName, key)) { + minio.removeObject(bucketName, file); + } + assertThat(minio.listObjects(bucketName, key)).isEmpty(); + } } 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 8d9dfea143c3..54bf31d7151b 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 @@ -20,7 +20,9 @@ import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; +import java.nio.file.Path; import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; @@ -76,4 +78,15 @@ protected String getMetadataLocation(String tableName) .getTable(getSession().getSchema().orElseThrow(), tableName).orElseThrow() .getParameters().get("metadata_location"); } + + @Override + protected void deleteDirectory(String location) + { + try { + deleteRecursively(Path.of(location), ALLOW_INSECURE); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } } 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 a8169f9c1888..e59e8a28d9e6 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 @@ -200,4 +200,15 @@ protected String getMetadataLocation(String tableName) .getTable(schema, tableName).orElseThrow() .getParameters().get("metadata_location"); } + + @Override + protected void deleteDirectory(String location) + { + try { + fileSystem.delete(new org.apache.hadoop.fs.Path(location), true); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedViewTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java similarity index 89% rename from plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedViewTest.java rename to plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java index 4d94e6623dc1..e39e4a881029 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedViewTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java @@ -17,7 +17,7 @@ import static io.trino.plugin.iceberg.IcebergQueryRunner.createIcebergQueryRunner; -public class TestIcebergMaterializedViewTest +public class TestIcebergMaterializedView extends BaseIcebergMaterializedViewTest { @Override @@ -27,12 +27,6 @@ protected DistributedQueryRunner createQueryRunner() return createIcebergQueryRunner(); } - @Override - protected String getSchemaName() - { - return "tpch"; - } - @Override protected String getSchemaDirectory() { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetadataFileOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetadataFileOperations.java index ed5d84fe5cdd..c25a39eb8fb4 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetadataFileOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetadataFileOperations.java @@ -35,6 +35,7 @@ import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.inject.util.Modules.EMPTY_MODULE; +import static io.trino.SystemSessionProperties.MIN_INPUT_SIZE_PER_TASK; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.metastore.file.FileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.iceberg.TestIcebergMetadataFileOperations.FileType.MANIFEST; @@ -63,6 +64,10 @@ public class TestIcebergMetadataFileOperations private static final Session TEST_SESSION = testSessionBuilder() .setCatalog("iceberg") .setSchema("test_schema") + // It is essential to disable DeterminePartitionCount rule since all queries in this test scans small + // amount of data which makes them run with single hash partition count. However, this test requires them + // to run over multiple nodes. + .setSystemProperty(MIN_INPUT_SIZE_PER_TASK, "0MB") .build(); private TrackingFileSystemFactory trackingFileSystemFactory; 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 cb8818db1c97..afda2c067913 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 @@ -32,6 +32,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Methods.CREATE_TABLE; +import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Methods.DROP_TABLE; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Methods.GET_DATABASE; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Methods.GET_TABLE; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Methods.REPLACE_TABLE; @@ -297,6 +298,19 @@ public void testSelectSystemTable() .containsExactly(DATA, HISTORY, SNAPSHOTS, MANIFESTS, PARTITIONS, FILES, PROPERTIES); } + @Test + public void testUnregisterTable() + { + assertUpdate("CREATE TABLE test_unregister_table AS SELECT 2 as age", 1); + + assertMetastoreInvocations("CALL system.unregister_table(CURRENT_SCHEMA, 'test_unregister_table')", + ImmutableMultiset.builder() + .add(GET_DATABASE) + .add(GET_TABLE) + .add(DROP_TABLE) + .build()); + } + private void assertMetastoreInvocations(@Language("SQL") String query, Multiset expectedInvocations) { assertMetastoreInvocations(getSession(), query, expectedInvocations); 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 2f888c0e4e8e..a22f6d57c8bc 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 @@ -18,6 +18,7 @@ import io.trino.testing.sql.TestTable; import org.testng.annotations.Test; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -73,4 +74,15 @@ protected Session withSmallRowGroups(Session session) .setCatalogSessionProperty("iceberg", "parquet_writer_batch_size", "10") .build(); } + + @Override + protected Optional filterSetColumnTypesDataProvider(SetColumnTypeSetup setup) + { + switch ("%s -> %s".formatted(setup.sourceColumnType(), setup.newColumnType())) { + case "row(x integer) -> row(y integer)": + // TODO https://github.com/trinodb/trino/issues/15822 The connector returns incorrect NULL when a field in row type doesn't exist in Parquet files + return Optional.of(setup.withNewValueLiteral("NULL")); + } + return super.filterSetColumnTypesDataProvider(setup); + } } 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 f2834ef7d2c5..9a4bca3d38dc 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 @@ -92,25 +92,7 @@ public void cleanup() getQueryRunner().execute("DROP SCHEMA IF EXISTS " + schemaName); // DROP TABLES should clean up any files, but clear the directory manually to be safe - AmazonS3 s3 = AmazonS3ClientBuilder.standard().build(); - - ListObjectsV2Request listObjectsRequest = new ListObjectsV2Request() - .withBucketName(bucketName) - .withPrefix(schemaPath()); - List keysToDelete = getPaginatedResults( - s3::listObjectsV2, - listObjectsRequest, - ListObjectsV2Request::setContinuationToken, - ListObjectsV2Result::getNextContinuationToken, - new AwsApiCallStats()) - .map(ListObjectsV2Result::getObjectSummaries) - .flatMap(objectSummaries -> objectSummaries.stream().map(S3ObjectSummary::getKey)) - .map(DeleteObjectsRequest.KeyVersion::new) - .collect(toImmutableList()); - - if (!keysToDelete.isEmpty()) { - s3.deleteObjects(new DeleteObjectsRequest(bucketName).withKeys(keysToDelete)); - } + deleteDirectory(schemaPath()); } @Test @@ -217,6 +199,31 @@ protected String getMetadataLocation(String tableName) .getParameters().get("metadata_location"); } + @Override + protected void deleteDirectory(String location) + { + AmazonS3 s3 = AmazonS3ClientBuilder.standard().build(); + + ListObjectsV2Request listObjectsRequest = new ListObjectsV2Request() + .withBucketName(bucketName) + .withPrefix(location); + List keysToDelete = getPaginatedResults( + s3::listObjectsV2, + listObjectsRequest, + ListObjectsV2Request::setContinuationToken, + ListObjectsV2Result::getNextContinuationToken, + new AwsApiCallStats()) + .map(ListObjectsV2Result::getObjectSummaries) + .flatMap(objectSummaries -> objectSummaries.stream().map(S3ObjectSummary::getKey)) + .map(DeleteObjectsRequest.KeyVersion::new) + .collect(toImmutableList()); + + if (!keysToDelete.isEmpty()) { + s3.deleteObjects(new DeleteObjectsRequest(bucketName).withKeys(keysToDelete)); + } + assertThat(s3.listObjects(bucketName, location).getObjectSummaries()).isEmpty(); + } + private String schemaPath() { return format("s3://%s/%s", bucketName, schemaName); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedViewTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java similarity index 96% rename from plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedViewTest.java rename to plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java index 4298e1e228f3..95b1d315d7ef 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedViewTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java @@ -38,7 +38,7 @@ import static io.trino.plugin.hive.metastore.glue.AwsSdkUtil.getPaginatedResults; import static io.trino.testing.TestingNames.randomNameSuffix; -public class TestIcebergGlueCatalogMaterializedViewTest +public class TestIcebergGlueCatalogMaterializedView extends BaseIcebergMaterializedViewTest { private final String schemaName = "test_iceberg_materialized_view_" + randomNameSuffix(); @@ -65,12 +65,6 @@ protected QueryRunner createQueryRunner() .build(); } - @Override - protected String getSchemaName() - { - return schemaName; - } - @Override protected String getSchemaDirectory() { 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 4e2b9986b459..73009d603ff7 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 @@ -54,6 +54,7 @@ protected QueryRunner createQueryRunner() .put("iceberg.catalog.type", "jdbc") .put("iceberg.jdbc-catalog.connection-url", server.getJdbcUrl()) .put("iceberg.jdbc-catalog.catalog-name", "tpch") + .put("iceberg.register-table-procedure.enabled", "true") .buildOrThrow()) .setInitialTables(REQUIRED_TPCH_TABLES) .build(); @@ -97,42 +98,35 @@ protected String getMetadataLocation(String tableName) public void testRegisterTableWithTableLocation() { assertThatThrownBy(super::testRegisterTableWithTableLocation) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg JDBC catalogs"); } @Override public void testRegisterTableWithComments() { assertThatThrownBy(super::testRegisterTableWithComments) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg JDBC catalogs"); } @Override public void testRegisterTableWithShowCreateTable() { assertThatThrownBy(super::testRegisterTableWithShowCreateTable) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg JDBC catalogs"); } @Override public void testRegisterTableWithReInsert() { assertThatThrownBy(super::testRegisterTableWithReInsert) - .hasMessageContaining("register_table procedure is disabled"); - } - - @Override - public void testRegisterTableWithDroppedTable() - { - assertThatThrownBy(super::testRegisterTableWithDroppedTable) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg JDBC catalogs"); } @Override public void testRegisterTableWithDifferentTableName() { assertThatThrownBy(super::testRegisterTableWithDifferentTableName) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg JDBC catalogs"); } @Override @@ -141,4 +135,38 @@ public void testRegisterTableWithMetadataFile() assertThatThrownBy(super::testRegisterTableWithMetadataFile) .hasMessageContaining("metadata location for register_table is not supported"); } + + @Override + public void testUnregisterTable() + { + assertThatThrownBy(super::testUnregisterTable) + .hasMessageContaining("unregisterTable is not supported for Iceberg JDBC catalogs"); + } + + @Override + public void testUnregisterBrokenTable() + { + assertThatThrownBy(super::testUnregisterBrokenTable) + .hasMessageContaining("unregisterTable is not supported for Iceberg JDBC catalogs"); + } + + @Override + public void testUnregisterTableNotExistingTable() + { + assertThatThrownBy(super::testUnregisterTableNotExistingTable) + .hasMessageContaining("unregisterTable is not supported for Iceberg JDBC catalogs"); + } + + @Override + public void testRepeatUnregisterTable() + { + assertThatThrownBy(super::testRepeatUnregisterTable) + .hasMessageContaining("unregisterTable is not supported for Iceberg JDBC catalogs"); + } + + @Override + protected void deleteDirectory(String location) + { + // used when unregistering a table, which is not supported by the JDBC catalog + } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergRestCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergRestCatalogConnectorSmokeTest.java index 72ebe12515c0..42d2e3296adc 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergRestCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergRestCatalogConnectorSmokeTest.java @@ -76,6 +76,7 @@ protected QueryRunner createQueryRunner() .put("iceberg.file-format", format.name()) .put("iceberg.catalog.type", "rest") .put("iceberg.rest-catalog.uri", testServer.getBaseUrl().toString()) + .put("iceberg.register-table-procedure.enabled", "true") .buildOrThrow()) .setInitialTables(REQUIRED_TPCH_TABLES) .build(); @@ -119,42 +120,35 @@ protected String getMetadataLocation(String tableName) public void testRegisterTableWithTableLocation() { assertThatThrownBy(super::testRegisterTableWithTableLocation) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); } @Override public void testRegisterTableWithComments() { assertThatThrownBy(super::testRegisterTableWithComments) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); } @Override public void testRegisterTableWithShowCreateTable() { assertThatThrownBy(super::testRegisterTableWithShowCreateTable) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); } @Override public void testRegisterTableWithReInsert() { assertThatThrownBy(super::testRegisterTableWithReInsert) - .hasMessageContaining("register_table procedure is disabled"); - } - - @Override - public void testRegisterTableWithDroppedTable() - { - assertThatThrownBy(super::testRegisterTableWithDroppedTable) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); } @Override public void testRegisterTableWithDifferentTableName() { assertThatThrownBy(super::testRegisterTableWithDifferentTableName) - .hasMessageContaining("register_table procedure is disabled"); + .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); } @Override @@ -163,4 +157,38 @@ public void testRegisterTableWithMetadataFile() assertThatThrownBy(super::testRegisterTableWithMetadataFile) .hasMessageContaining("metadata location for register_table is not supported"); } + + @Override + public void testUnregisterTable() + { + assertThatThrownBy(super::testUnregisterTable) + .hasMessageContaining("unregisterTable is not supported for Iceberg REST catalogs"); + } + + @Override + public void testUnregisterBrokenTable() + { + assertThatThrownBy(super::testUnregisterBrokenTable) + .hasMessageContaining("unregisterTable is not supported for Iceberg REST catalogs"); + } + + @Override + public void testUnregisterTableNotExistingTable() + { + assertThatThrownBy(super::testUnregisterTableNotExistingTable) + .hasMessageContaining("unregisterTable is not supported for Iceberg REST catalogs"); + } + + @Override + public void testRepeatUnregisterTable() + { + assertThatThrownBy(super::testRepeatUnregisterTable) + .hasMessageContaining("unregisterTable is not supported for Iceberg REST catalogs"); + } + + @Override + protected void deleteDirectory(String location) + { + // used when unregistering a table, which is not supported by the REST catalog + } } diff --git a/plugin/trino-jmx/pom.xml b/plugin/trino-jmx/pom.xml index 5bd9fa9a2155..724037a3bff5 100644 --- a/plugin/trino-jmx/pom.xml +++ b/plugin/trino-jmx/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index 2a27780b4da9..7922d1de361e 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/JsonRowEncoder.java b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/JsonRowEncoder.java index 93de2ecdc855..55284bf741dd 100644 --- a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/JsonRowEncoder.java +++ b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/JsonRowEncoder.java @@ -46,7 +46,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.TinyintType.TINYINT; @@ -111,7 +111,7 @@ private static boolean isSupportedTemporalType(Type type) { return type.equals(DATE) || type.equals(TIME_MILLIS) || - type.equals(TIME_WITH_TIME_ZONE) || + type.equals(TIME_TZ_MILLIS) || type.equals(TIMESTAMP_MILLIS) || type.equals(TIMESTAMP_TZ_MILLIS); } diff --git a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/format/CustomDateTimeFormatter.java b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/format/CustomDateTimeFormatter.java index 5ae366587f07..8e76b8639f59 100644 --- a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/format/CustomDateTimeFormatter.java +++ b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/format/CustomDateTimeFormatter.java @@ -35,7 +35,7 @@ import static io.trino.plugin.kafka.encoder.json.format.util.TimeConversions.scalePicosToMillis; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static java.util.concurrent.TimeUnit.DAYS; @@ -50,7 +50,7 @@ public static boolean isSupportedType(Type type) { return type.equals(DATE) || type.equals(TIME_MILLIS) || - type.equals(TIME_WITH_TIME_ZONE) || + type.equals(TIME_TZ_MILLIS) || type.equals(TIMESTAMP_MILLIS) || type.equals(TIMESTAMP_TZ_MILLIS); } diff --git a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/format/ISO8601DateTimeFormatter.java b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/format/ISO8601DateTimeFormatter.java index abd764cd6eda..65b5b1628ff5 100644 --- a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/format/ISO8601DateTimeFormatter.java +++ b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/encoder/json/format/ISO8601DateTimeFormatter.java @@ -37,7 +37,7 @@ import static io.trino.plugin.kafka.encoder.json.format.util.TimeConversions.scalePicosToNanos; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static java.time.ZoneOffset.UTC; @@ -51,7 +51,7 @@ public static boolean isSupportedType(Type type) { return type.equals(DATE) || type.equals(TIME_MILLIS) || - type.equals(TIME_WITH_TIME_ZONE) || + type.equals(TIME_TZ_MILLIS) || type.equals(TIMESTAMP_MILLIS) || type.equals(TIMESTAMP_TZ_MILLIS); } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java index 96f99e4e23f4..11923f53ec09 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java @@ -53,7 +53,7 @@ import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.VarcharType.createVarcharType; @@ -533,7 +533,7 @@ private static List jsonDateTimeFormatsData() .setTopicName(JSON_CUSTOM_DATE_TIME_TABLE_NAME) .addField(DATE, CUSTOM_DATE_TIME.toString(), "yyyy-MM-dd", "DATE '2020-07-15'") .addField(TIME_MILLIS, CUSTOM_DATE_TIME.toString(), "HH:mm:ss.SSS", "TIME '01:02:03.456'") - .addField(TIME_WITH_TIME_ZONE, CUSTOM_DATE_TIME.toString(), "HH:mm:ss.SSS Z", "TIME '01:02:03.456 -04:00'") + .addField(TIME_TZ_MILLIS, CUSTOM_DATE_TIME.toString(), "HH:mm:ss.SSS Z", "TIME '01:02:03.456 -04:00'") .addField(TIMESTAMP_MILLIS, CUSTOM_DATE_TIME.toString(), "yyyy-dd-MM HH:mm:ss.SSS", "TIMESTAMP '2020-07-15 01:02:03.456'") .addField(TIMESTAMP_TZ_MILLIS, CUSTOM_DATE_TIME.toString(), "yyyy-dd-MM HH:mm:ss.SSS Z", "TIMESTAMP '2020-07-15 01:02:03.456 -04:00'") .build()) @@ -541,7 +541,7 @@ private static List jsonDateTimeFormatsData() .setTopicName(JSON_ISO8601_TABLE_NAME) .addField(DATE, ISO8601.toString(), "DATE '2020-07-15'") .addField(TIME_MILLIS, ISO8601.toString(), "TIME '01:02:03.456'") - .addField(TIME_WITH_TIME_ZONE, ISO8601.toString(), "TIME '01:02:03.456 -04:00'") + .addField(TIME_TZ_MILLIS, ISO8601.toString(), "TIME '01:02:03.456 -04:00'") .addField(TIMESTAMP_MILLIS, ISO8601.toString(), "TIMESTAMP '2020-07-15 01:02:03.456'") .addField(TIMESTAMP_TZ_MILLIS, ISO8601.toString(), "TIMESTAMP '2020-07-15 01:02:03.456 -04:00'") .build()) diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestJsonEncoder.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestJsonEncoder.java index 6458c3c578e9..3ab85f54a25c 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestJsonEncoder.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestJsonEncoder.java @@ -39,7 +39,7 @@ import static io.trino.spi.type.RealType.REAL; import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.TinyintType.TINYINT; @@ -113,7 +113,7 @@ public void testColumnValidation() for (DateTimeFormat dataFormat : ImmutableList.of(CUSTOM_DATE_TIME, ISO8601)) { assertSupportedDataType(() -> singleColumnEncoder(DATE, dataFormat, "yyyy-dd-MM")); assertSupportedDataType(() -> singleColumnEncoder(TIME_MILLIS, dataFormat, "kk:mm:ss.SSS")); - assertSupportedDataType(() -> singleColumnEncoder(TIME_WITH_TIME_ZONE, dataFormat, "kk:mm:ss.SSS Z")); + assertSupportedDataType(() -> singleColumnEncoder(TIME_TZ_MILLIS, dataFormat, "kk:mm:ss.SSS Z")); assertSupportedDataType(() -> singleColumnEncoder(TIMESTAMP_MILLIS, dataFormat, "yyyy-dd-MM kk:mm:ss.SSS")); assertSupportedDataType(() -> singleColumnEncoder(TIMESTAMP_TZ_MILLIS, dataFormat, "yyyy-dd-MM kk:mm:ss.SSS Z")); } @@ -130,7 +130,7 @@ public void testColumnValidation() assertUnsupportedDataFormatException(() -> singleColumnEncoder(DATE)); assertUnsupportedDataFormatException(() -> singleColumnEncoder(TIME_MILLIS)); - assertUnsupportedDataFormatException(() -> singleColumnEncoder(TIME_WITH_TIME_ZONE)); + assertUnsupportedDataFormatException(() -> singleColumnEncoder(TIME_TZ_MILLIS)); assertUnsupportedDataFormatException(() -> singleColumnEncoder(TIMESTAMP_MILLIS)); assertUnsupportedDataFormatException(() -> singleColumnEncoder(TIMESTAMP_TZ_MILLIS)); diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index 3e6b4944a203..8812f43d27c4 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index bb3dd24dc000..dffcfd627888 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-local-file/pom.xml b/plugin/trino-local-file/pom.xml index c912fdd10cea..9d0e2bdab605 100644 --- a/plugin/trino-local-file/pom.xml +++ b/plugin/trino-local-file/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index 7a0b4aed7a51..03325525be43 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index 3ac2378eb074..9e8b116f7e83 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-ml/pom.xml b/plugin/trino-ml/pom.xml index 80f0731398fe..96b4c7407fb0 100644 --- a/plugin/trino-ml/pom.xml +++ b/plugin/trino-ml/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index 9af08aa107cc..308715d071c2 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mysql-event-listener/pom.xml b/plugin/trino-mysql-event-listener/pom.xml index a462df91f1b2..29b989d443ef 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 - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index ffcbed8ffedf..beefd5cbe9e3 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index b68244ebd20e..18b2ae671ad0 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index 039cb1f8a196..d79733ccee52 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index e15a83010ce7..900507a4edf5 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/PhoenixClient.java b/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/PhoenixClient.java index e448b6ca5a3f..b772a8b47379 100644 --- a/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/PhoenixClient.java +++ b/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/PhoenixClient.java @@ -174,8 +174,6 @@ import static io.trino.spi.type.RealType.REAL; import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; -import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static java.lang.Math.max; @@ -548,10 +546,6 @@ public WriteMapping toWriteMapping(ConnectorSession session, Type type) if (TIME_MILLIS.equals(type)) { return WriteMapping.longMapping("time", timeWriteFunctionUsingSqlTime()); } - // Phoenix doesn't support _WITH_TIME_ZONE - if (TIME_WITH_TIME_ZONE.equals(type) || TIMESTAMP_TZ_MILLIS.equals(type)) { - throw new TrinoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); - } if (type instanceof ArrayType arrayType) { Type elementType = arrayType.getElementType(); String elementDataType = toWriteMapping(session, elementType).getDataType().toUpperCase(ENGLISH); diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index e1047de75cdf..5a77b3126226 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -4,7 +4,7 @@ trino-root io.trino - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index 78472cdbfdda..a172a3fe0c9e 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlJdbcConnectionCreation.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlJdbcConnectionCreation.java index 236d551f406d..8ef14b24945d 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlJdbcConnectionCreation.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlJdbcConnectionCreation.java @@ -86,7 +86,7 @@ public Object[][] testCases() {"SELECT * FROM information_schema.schemata", 1, Optional.empty()}, {"SELECT * FROM information_schema.tables", 1, Optional.empty()}, {"SELECT * FROM information_schema.columns", 1, Optional.empty()}, - {"SELECT * FROM nation", 2, Optional.empty()}, + {"SELECT * FROM nation", 3, Optional.empty()}, {"SELECT * FROM TABLE (system.query(query => 'SELECT * FROM tpch.nation'))", 2, Optional.empty()}, {"CREATE TABLE copy_of_nation AS SELECT * FROM nation", 6, Optional.empty()}, {"INSERT INTO copy_of_nation SELECT * FROM nation", 6, Optional.empty()}, diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 7c9ceaa31612..820eefdf6fe2 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index 8c4ec97c5b25..23e64b55d6ec 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index a2a8f7ba8082..4844fb25140a 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/decoder/hash/ISO8601HashRedisFieldDecoder.java b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/decoder/hash/ISO8601HashRedisFieldDecoder.java index d7b089e7fc7d..f2396c5c67a6 100644 --- a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/decoder/hash/ISO8601HashRedisFieldDecoder.java +++ b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/decoder/hash/ISO8601HashRedisFieldDecoder.java @@ -26,7 +26,7 @@ import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; @@ -72,11 +72,11 @@ public long getLong() if (type.equals(TIME_MILLIS)) { return millis * PICOSECONDS_PER_MILLISECOND; } - if (type.equals(TIMESTAMP_TZ_MILLIS) || type.equals(TIME_WITH_TIME_ZONE)) { + if (type.equals(TIMESTAMP_TZ_MILLIS) || type.equals(TIME_TZ_MILLIS)) { return packDateTimeWithZone(millis, getTimeZoneKey(dateTime.getZone().getID())); } - return millis; + throw new IllegalStateException("Unsupported type: " + type); } } } diff --git a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/util/RedisLoader.java b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/util/RedisLoader.java index 9bb128da0a76..6d23dd40818e 100644 --- a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/util/RedisLoader.java +++ b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/util/RedisLoader.java @@ -45,7 +45,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.TimeType.TIME_MILLIS; -import static io.trino.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; +import static io.trino.spi.type.TimeWithTimeZoneType.TIME_TZ_MILLIS; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.util.DateTimeUtils.parseLegacyTime; @@ -171,7 +171,7 @@ private Object convertValue(Object value, Type type) if (TIMESTAMP_MILLIS.equals(type)) { return ISO8601_FORMATTER.print(castToShortTimestamp(TIMESTAMP_MILLIS.getPrecision(), (String) value)); } - if (TIME_WITH_TIME_ZONE.equals(type) || TIMESTAMP_TZ_MILLIS.equals(type)) { + if (TIME_TZ_MILLIS.equals(type) || TIMESTAMP_TZ_MILLIS.equals(type)) { return ISO8601_FORMATTER.print(unpackMillisUtc(DateTimeUtils.convertToTimestampWithTimeZone(timeZoneKey, (String) value))); } throw new AssertionError("unhandled type: " + type); diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index 11b943fed412..46e67c6c7b1d 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClient.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClient.java index 86abef905cfd..61975c21a47d 100644 --- a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClient.java +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClient.java @@ -679,6 +679,8 @@ public WriteMapping toWriteMapping(ConnectorSession session, Type type) } // Fall back to legacy behavior + // TODO we should not fall back to legacy behavior, the mappings should be explicit (the legacyToWriteMapping + // is just a copy of some generic default mappings that used to exist) return legacyToWriteMapping(type); } diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index b13cdd4fd2a0..ab36610f8126 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 - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index b1eb4d2e83e4..7bb31014d9ca 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 - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index 86931acfe5ed..71165ab5291f 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index 85d934f575f0..d3b1d6e082c2 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-teradata-functions/pom.xml b/plugin/trino-teradata-functions/pom.xml index 4d1a0a0cc6a6..5fbcd684a4a5 100644 --- a/plugin/trino-teradata-functions/pom.xml +++ b/plugin/trino-teradata-functions/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-api/pom.xml b/plugin/trino-thrift-api/pom.xml index 850ebfefab62..be1b695286e1 100644 --- a/plugin/trino-thrift-api/pom.xml +++ b/plugin/trino-thrift-api/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index 70fd62463e9c..e86009b886db 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 - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index 1d6d54f9d9b8..a5811c35d8e3 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpcds/pom.xml b/plugin/trino-tpcds/pom.xml index 2025cc97fe3c..b0e18405a424 100644 --- a/plugin/trino-tpcds/pom.xml +++ b/plugin/trino-tpcds/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpch/pom.xml b/plugin/trino-tpch/pom.xml index 2e454c490c43..3a5f72bfd218 100644 --- a/plugin/trino-tpch/pom.xml +++ b/plugin/trino-tpch/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/pom.xml b/pom.xml index 580ba5065144..92efc51d0e33 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ io.airlift airbase - 132 + 133 io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT trino-root Trino @@ -49,7 +49,7 @@ 1.7.4 2.7.7-1 4.11.1 - 222 + 223 9.0.0 ${dep.airlift.version} 1.12.261 @@ -955,14 +955,7 @@ io.airlift.discovery discovery-server - 1.30 - - - - javax.servlet - javax.servlet-api - - + 1.32 diff --git a/service/trino-proxy/pom.xml b/service/trino-proxy/pom.xml index e773f52f7e17..9cbb803e91cb 100644 --- a/service/trino-proxy/pom.xml +++ b/service/trino-proxy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/service/trino-verifier/pom.xml b/service/trino-verifier/pom.xml index 2efb48635281..6bb43e85d195 100644 --- a/service/trino-verifier/pom.xml +++ b/service/trino-verifier/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchmark-queries/pom.xml b/testing/trino-benchmark-queries/pom.xml index 97a2b92ef236..6401cc934e73 100644 --- a/testing/trino-benchmark-queries/pom.xml +++ b/testing/trino-benchmark-queries/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchmark/pom.xml b/testing/trino-benchmark/pom.xml index 02dafa688d93..9772a4bf7713 100644 --- a/testing/trino-benchmark/pom.xml +++ b/testing/trino-benchmark/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchto-benchmarks/pom.xml b/testing/trino-benchto-benchmarks/pom.xml index 7f90cf427ad2..af1ede458f11 100644 --- a/testing/trino-benchto-benchmarks/pom.xml +++ b/testing/trino-benchto-benchmarks/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index 058f92b37a18..7eb9883f8278 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/BaseFaultTolerantExecutionTest.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/BaseFaultTolerantExecutionTest.java index 3c71aa0ec448..f0a3636954ce 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/BaseFaultTolerantExecutionTest.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/BaseFaultTolerantExecutionTest.java @@ -66,7 +66,7 @@ public void testExecutePreferredWritePartitioningSkewMitigation() { @Language("SQL") String createTableSql = """ CREATE TABLE test_execute_skew_mitigation WITH (%s = ARRAY['returnflag']) AS - SELECT orderkey, partkey, suppkey, linenumber, quantity, extendedprice, discount, tax, linestatus, shipdate, commitdate, receiptdate, shipinstruct, shipmode, comment, returnflag + SELECT orderkey, partkey, suppkey, linenumber, quantity, extendedprice, discount, tax, linestatus, shipdate, commitdate, receiptdate, shipinstruct, shipmode, returnflag FROM tpch.sf1.lineitem WHERE returnflag = 'N' LIMIT 1000000""".formatted(partitioningTablePropertyName); diff --git a/testing/trino-plugin-reader/pom.xml b/testing/trino-plugin-reader/pom.xml index 96379b69f669..4ca23a71c183 100644 --- a/testing/trino-plugin-reader/pom.xml +++ b/testing/trino-plugin-reader/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests-launcher/pom.xml b/testing/trino-product-tests-launcher/pom.xml index daceb708c823..a0a4587236e8 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 - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests/pom.xml b/testing/trino-product-tests/pom.xml index 7d8051ab86b3..3817deea3610 100644 --- a/testing/trino-product-tests/pom.xml +++ b/testing/trino-product-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergSparkCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergSparkCompatibility.java index 597299624713..c2a035e3f973 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergSparkCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergSparkCompatibility.java @@ -72,6 +72,7 @@ import static io.trino.tests.product.iceberg.TestIcebergSparkCompatibility.CreateMode.CREATE_TABLE_WITH_NO_DATA_AND_INSERT; import static io.trino.tests.product.iceberg.util.IcebergTestUtils.getTableLocation; import static io.trino.tests.product.iceberg.util.IcebergTestUtils.stripNamenodeURI; +import static io.trino.tests.product.utils.QueryExecutors.onHive; import static io.trino.tests.product.utils.QueryExecutors.onSpark; import static io.trino.tests.product.utils.QueryExecutors.onTrino; import static java.lang.String.format; @@ -504,7 +505,7 @@ private void testSparkPartitionedByNaN(String typeName, StorageFormat storageFor } @Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS}) - public void testPartitionedByNestedFiled() + public void testPartitionedByNestedField() { String baseTableName = "test_trino_nested_field_partition_" + randomNameSuffix(); String trinoTableName = trinoTableName(baseTableName); @@ -2344,7 +2345,7 @@ public void testTrinoAnalyze() @Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS}) public void testTrinoAnalyzeWithNonLowercaseColumnName() { - String baseTableName = "test_trino_analyze_with_uppercase_filed" + randomNameSuffix(); + String baseTableName = "test_trino_analyze_with_uppercase_field" + randomNameSuffix(); String trinoTableName = trinoTableName(baseTableName); String sparkTableName = sparkTableName(baseTableName); @@ -2546,6 +2547,26 @@ public void testRegisterTableWithMetadataFile(StorageFormat storageFormat) onTrino().executeQuery(format("DROP TABLE %s", trinoTableName)); } + @Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS}) + public void testUnregisterNotIcebergTable() + { + String baseTableName = "test_unregister_not_iceberg_table_" + randomNameSuffix(); + String trinoTableName = trinoTableName(baseTableName); + String hiveTableName = TEST_SCHEMA_NAME + "." + baseTableName; + + onHive().executeQuery("CREATE TABLE " + hiveTableName + " AS SELECT 1 a"); + + assertThatThrownBy(() -> onTrino().executeQuery("CALL iceberg.system.unregister_table('default', '" + baseTableName + "')")) + .hasMessageContaining("Not an Iceberg table"); + + assertThat(onSpark().executeQuery("SELECT * FROM " + hiveTableName)).containsOnly(row(1)); + assertThat(onTrino().executeQuery("SELECT * FROM hive.default." + baseTableName)).containsOnly(row(1)); + assertThatThrownBy(() -> onTrino().executeQuery("SELECT * FROM " + trinoTableName)) + .hasMessageContaining("Not an Iceberg table"); + + onHive().executeQuery("DROP TABLE " + hiveTableName); + } + @Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS}, dataProvider = "testSetColumnTypeDataProvider") public void testTrinoSetColumnType(StorageFormat storageFormat, String sourceColumnType, String sourceValueLiteral, String newColumnType, Object newValue) { @@ -2591,6 +2612,50 @@ public static Object[][] testSetColumnTypeDataProvider() }); } + @Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS}, dataProvider = "storageFormats") + public void testTrinoAlterStructColumnType(StorageFormat storageFormat) + { + String baseTableName = "test_trino_alter_row_column_type_" + randomNameSuffix(); + String trinoTableName = trinoTableName(baseTableName); + String sparkTableName = sparkTableName(baseTableName); + + onTrino().executeQuery("CREATE TABLE " + trinoTableName + " " + + "WITH (format = '" + storageFormat + "')" + + "AS SELECT CAST(row(1, 2) AS row(a integer, b integer)) AS col"); + + // Add a nested field + onTrino().executeQuery("ALTER TABLE " + trinoTableName + " ALTER COLUMN col SET DATA TYPE row(a integer, b integer, c integer)"); + assertEquals(getColumnType(baseTableName, "col"), "row(a integer, b integer, c integer)"); + assertThat(onSpark().executeQuery("SELECT col.a, col.b, col.c FROM " + sparkTableName)).containsOnly(row(1, 2, null)); + assertThat(onTrino().executeQuery("SELECT col.a, col.b, col.c FROM " + trinoTableName)).containsOnly(row(1, 2, null)); + + // Update a nested field + onTrino().executeQuery("ALTER TABLE " + trinoTableName + " ALTER COLUMN col SET DATA TYPE row(a integer, b bigint, c integer)"); + assertEquals(getColumnType(baseTableName, "col"), "row(a integer, b bigint, c integer)"); + assertThat(onSpark().executeQuery("SELECT col.a, col.b, col.c FROM " + sparkTableName)).containsOnly(row(1, 2, null)); + assertThat(onTrino().executeQuery("SELECT col.a, col.b, col.c FROM " + trinoTableName)).containsOnly(row(1, 2, null)); + + // Drop a nested field + onTrino().executeQuery("ALTER TABLE " + trinoTableName + " ALTER COLUMN col SET DATA TYPE row(a integer, c integer)"); + assertEquals(getColumnType(baseTableName, "col"), "row(a integer, c integer)"); + assertThat(onSpark().executeQuery("SELECT col.a, col.c FROM " + sparkTableName)).containsOnly(row(1, null)); + assertThat(onTrino().executeQuery("SELECT col.a, col.c FROM " + trinoTableName)).containsOnly(row(1, null)); + + // Adding a nested field with the same name doesn't restore the old data + onTrino().executeQuery("ALTER TABLE " + trinoTableName + " ALTER COLUMN col SET DATA TYPE row(a integer, c integer, b bigint)"); + assertEquals(getColumnType(baseTableName, "col"), "row(a integer, c integer, b bigint)"); + assertThat(onSpark().executeQuery("SELECT col.a, col.c, col.b FROM " + sparkTableName)).containsOnly(row(1, null, null)); + assertThat(onTrino().executeQuery("SELECT col.a, col.c, col.b FROM " + trinoTableName)).containsOnly(row(1, null, null)); + + // Reorder fields + onTrino().executeQuery("ALTER TABLE " + trinoTableName + " ALTER COLUMN col SET DATA TYPE row(c integer, b bigint, a integer)"); + assertEquals(getColumnType(baseTableName, "col"), "row(c integer, b bigint, a integer)"); + assertThat(onSpark().executeQuery("SELECT col.b, col.c, col.a FROM " + sparkTableName)).containsOnly(row(null, null, 1)); + assertThat(onTrino().executeQuery("SELECT col.b, col.c, col.a FROM " + trinoTableName)).containsOnly(row(null, null, 1)); + + onTrino().executeQuery("DROP TABLE " + trinoTableName); + } + @Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS}, dataProvider = "testSparkAlterColumnType") public void testSparkAlterColumnType(StorageFormat storageFormat, String sourceColumnType, String sourceValueLiteral, String newColumnType, Object newValue) { @@ -2637,6 +2702,50 @@ public static Object[][] testSparkAlterColumnType() }); } + @Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS}, dataProvider = "storageFormats") + public void testSparkAlterStructColumnType(StorageFormat storageFormat) + { + String baseTableName = "test_spark_alter_struct_column_type_" + randomNameSuffix(); + String trinoTableName = trinoTableName(baseTableName); + String sparkTableName = sparkTableName(baseTableName); + + onSpark().executeQuery("CREATE TABLE " + sparkTableName + + " TBLPROPERTIES ('write.format.default' = '" + storageFormat + "')" + + "AS SELECT named_struct('a', 1, 'b', 2) AS col"); + + // Add a nested field + onSpark().executeQuery("ALTER TABLE " + sparkTableName + " ADD COLUMN col.c integer"); + assertEquals(getColumnType(baseTableName, "col"), "row(a integer, b integer, c integer)"); + assertThat(onSpark().executeQuery("SELECT col.a, col.b, col.c FROM " + sparkTableName)).containsOnly(row(1, 2, null)); + assertThat(onTrino().executeQuery("SELECT col.a, col.b, col.c FROM " + trinoTableName)).containsOnly(row(1, 2, null)); + + // Update a nested field + onSpark().executeQuery("ALTER TABLE " + sparkTableName + " ALTER COLUMN col.b TYPE bigint"); + assertEquals(getColumnType(baseTableName, "col"), "row(a integer, b bigint, c integer)"); + assertThat(onSpark().executeQuery("SELECT col.a, col.b, col.c FROM " + sparkTableName)).containsOnly(row(1, 2, null)); + assertThat(onTrino().executeQuery("SELECT col.a, col.b, col.c FROM " + trinoTableName)).containsOnly(row(1, 2, null)); + + // Drop a nested field + onSpark().executeQuery("ALTER TABLE " + sparkTableName + " DROP COLUMN col.b"); + assertEquals(getColumnType(baseTableName, "col"), "row(a integer, c integer)"); + assertThat(onSpark().executeQuery("SELECT col.a, col.c FROM " + sparkTableName)).containsOnly(row(1, null)); + assertThat(onTrino().executeQuery("SELECT col.a, col.c FROM " + trinoTableName)).containsOnly(row(1, null)); + + // Adding a nested field with the same name doesn't restore the old data + onSpark().executeQuery("ALTER TABLE " + sparkTableName + " ADD COLUMN col.b bigint"); + assertEquals(getColumnType(baseTableName, "col"), "row(a integer, c integer, b bigint)"); + assertThat(onSpark().executeQuery("SELECT col.a, col.c, col.b FROM " + sparkTableName)).containsOnly(row(1, null, null)); + assertThat(onTrino().executeQuery("SELECT col.a, col.c, col.b FROM " + trinoTableName)).containsOnly(row(1, null, null)); + + // Reorder fields + onSpark().executeQuery("ALTER TABLE " + sparkTableName + " ALTER COLUMN col.a AFTER b"); + assertEquals(getColumnType(baseTableName, "col"), "row(c integer, b bigint, a integer)"); + assertThat(onSpark().executeQuery("SELECT col.b, col.c, col.a FROM " + sparkTableName)).containsOnly(row(null, null, 1)); + assertThat(onTrino().executeQuery("SELECT col.b, col.c, col.a FROM " + trinoTableName)).containsOnly(row(null, null, 1)); + + onSpark().executeQuery("DROP TABLE " + sparkTableName); + } + private String getColumnType(String tableName, String columnName) { return (String) onTrino().executeQuery("SELECT data_type FROM " + TRINO_CATALOG + ".information_schema.columns " + diff --git a/testing/trino-server-dev/pom.xml b/testing/trino-server-dev/pom.xml index 9eeb54c4fbb5..1d744c1e1f22 100644 --- a/testing/trino-server-dev/pom.xml +++ b/testing/trino-server-dev/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-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 76ce09c04138..83ec69a44992 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 - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml @@ -14,7 +14,7 @@ ${project.parent.basedir} - 406-SNAPSHOT + 407-SNAPSHOT diff --git a/testing/trino-test-jdbc-compatibility-old-server/pom.xml b/testing/trino-test-jdbc-compatibility-old-server/pom.xml index a3b94421f3ae..7ea194a9881d 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 - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-containers/pom.xml b/testing/trino-testing-containers/pom.xml index a5a308303d64..55e9bf1aeff1 100644 --- a/testing/trino-testing-containers/pom.xml +++ b/testing/trino-testing-containers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-containers/src/main/java/io/trino/testing/minio/MinioClient.java b/testing/trino-testing-containers/src/main/java/io/trino/testing/minio/MinioClient.java index 77f14ef57329..62eaabd1078b 100644 --- a/testing/trino-testing-containers/src/main/java/io/trino/testing/minio/MinioClient.java +++ b/testing/trino-testing-containers/src/main/java/io/trino/testing/minio/MinioClient.java @@ -29,6 +29,7 @@ import io.minio.ListenBucketNotificationArgs; import io.minio.MakeBucketArgs; import io.minio.PutObjectArgs; +import io.minio.RemoveObjectArgs; import io.minio.Result; import io.minio.messages.Event; import io.minio.messages.NotificationRecords; @@ -227,6 +228,19 @@ public void copyObject(String sourceBucket, String sourceKey, String targetBucke } } + public void removeObject(String bucket, String key) + { + try { + client.removeObject(RemoveObjectArgs.builder() + .bucket(bucket) + .object(key) + .build()); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override public void close() { diff --git a/testing/trino-testing-kafka/pom.xml b/testing/trino-testing-kafka/pom.xml index b0f083c17535..af3baf679abd 100644 --- a/testing/trino-testing-kafka/pom.xml +++ b/testing/trino-testing-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-resources/pom.xml b/testing/trino-testing-resources/pom.xml index 4777eaf7e72c..a78a81a2b032 100644 --- a/testing/trino-testing-resources/pom.xml +++ b/testing/trino-testing-resources/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-services/pom.xml b/testing/trino-testing-services/pom.xml index 599bf0e16778..e262bcc0b230 100644 --- a/testing/trino-testing-services/pom.xml +++ b/testing/trino-testing-services/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index 5f1835ad00c9..14a6ca4486fd 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-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 a06d5ff82f0f..ddecb597a112 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 @@ -542,13 +542,13 @@ protected void executeExclusively(Runnable executionBlock) } } - protected String formatSqlText(String sql) + protected String formatSqlText(@Language("SQL") String sql) { return formatSql(SQL_PARSER.createStatement(sql, createParsingOptions(getSession()))); } //TODO: should WarningCollector be added? - protected String getExplainPlan(String query, ExplainType.Type planType) + protected String getExplainPlan(@Language("SQL") String query, ExplainType.Type planType) { QueryExplainer explainer = queryRunner.getQueryExplainer(); return newTransaction() @@ -558,7 +558,7 @@ protected String getExplainPlan(String query, ExplainType.Type planType) }); } - protected String getGraphvizExplainPlan(String query, ExplainType.Type planType) + protected String getGraphvizExplainPlan(@Language("SQL") String query, ExplainType.Type planType) { QueryExplainer explainer = queryRunner.getQueryExplainer(); return newTransaction() 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 9475fa9a8e37..43f56efeb10b 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 @@ -1018,12 +1018,12 @@ public void testMaterializedView() "SELECT table_name, table_type FROM information_schema.tables " + "WHERE table_schema = '" + view.getSchemaName() + "'")) .skippingTypesCheck() - .containsAll("VALUES ('" + view.getObjectName() + "', 'BASE TABLE')"); // TODO table_type should probably be "* VIEW" + .containsAll("VALUES ('" + view.getObjectName() + "', 'MATERIALIZED VIEW')"); // information_schema.tables with table_name filter assertQuery( "SELECT table_name, table_type FROM information_schema.tables " + "WHERE table_schema = '" + view.getSchemaName() + "' and table_name = '" + view.getObjectName() + "'", - "VALUES ('" + view.getObjectName() + "', 'BASE TABLE')"); + "VALUES ('" + view.getObjectName() + "', 'MATERIALIZED VIEW')"); // system.jdbc.tables without filter assertThat(query("SELECT table_schem, table_name, table_type FROM system.jdbc.tables")) @@ -2224,7 +2224,7 @@ public void testSetColumnTypes(SetColumnTypeSetup setup) assertEquals(getColumnType(table.getName(), "col"), setup.newColumnType); assertThat(query("SELECT * FROM " + table.getName())) .skippingTypesCheck() - .matches("VALUES " + setup.newValueLiteral); + .matches("SELECT " + setup.newValueLiteral); } catch (Exception e) { verifyUnsupportedTypeException(e, setup.sourceColumnType); @@ -2273,6 +2273,13 @@ private List setColumnTypeSetupData() .add(new SetColumnTypeSetup("varchar", "'varchar-to-char'", "char(20)")) .add(new SetColumnTypeSetup("array(integer)", "array[1]", "array(bigint)")) .add(new SetColumnTypeSetup("row(x integer)", "row(1)", "row(x bigint)")) + .add(new SetColumnTypeSetup("row(x integer)", "row(1)", "row(y integer)", "cast(row(NULL) as row(x integer))")) // rename a field + .add(new SetColumnTypeSetup("row(x integer)", "row(1)", "row(x integer, y integer)", "cast(row(1, NULL) as row(x integer, y integer))")) // add a new field + .add(new SetColumnTypeSetup("row(x integer, y integer)", "row(1, 2)", "row(x integer)", "cast(row(1) as row(x integer))")) // remove an existing field + .add(new SetColumnTypeSetup("row(x integer, y integer)", "row(1, 2)", "row(y integer, x integer)", "cast(row(2, 1) as row(y integer, x integer))")) // reorder fields + .add(new SetColumnTypeSetup("row(x integer, y integer)", "row(1, 2)", "row(z integer, y integer, x integer)", "cast(row(null, 2, 1) as row(z integer, y integer, x integer))")) // reorder fields with a new field + .add(new SetColumnTypeSetup("row(x row(nested integer))", "row(row(1))", "row(x row(nested bigint))", "cast(row(row(1)) as row(x row(nested bigint)))")) // update a nested field + .add(new SetColumnTypeSetup("row(x row(a integer, b integer))", "row(row(1, 2))", "row(x row(b integer, a integer))", "cast(row(row(2, 1)) as row(x row(b integer, a integer)))")) // reorder a nested field .build(); } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/H2QueryRunner.java b/testing/trino-testing/src/main/java/io/trino/testing/H2QueryRunner.java index b401bce4f3f4..f86038c995d5 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/H2QueryRunner.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/H2QueryRunner.java @@ -26,6 +26,7 @@ import io.trino.spi.type.CharType; import io.trino.spi.type.DecimalType; import io.trino.spi.type.TimeType; +import io.trino.spi.type.TimeWithTimeZoneType; import io.trino.spi.type.TimestampType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; @@ -71,7 +72,6 @@ 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.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.UuidType.UUID; @@ -328,7 +328,7 @@ else if (type instanceof TimeType) { row.add(timeValue); } } - else if (TIME_WITH_TIME_ZONE.equals(type)) { + else if (type instanceof TimeWithTimeZoneType) { throw new UnsupportedOperationException("H2 does not support TIME WITH TIME ZONE"); } else if (type instanceof TimestampType) { diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index a7593a4091a6..b4a83b9b427e 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 406-SNAPSHOT + 407-SNAPSHOT ../../pom.xml diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestQueues.java b/testing/trino-tests/src/test/java/io/trino/execution/TestQueues.java index 427e09153634..60a9fd313c7e 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestQueues.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestQueues.java @@ -32,7 +32,7 @@ import static io.airlift.units.DataSize.Unit.MEGABYTE; import static io.airlift.units.DataSize.Unit.TERABYTE; -import static io.trino.SystemSessionProperties.HASH_PARTITION_COUNT; +import static io.trino.SystemSessionProperties.MAX_HASH_PARTITION_COUNT; import static io.trino.execution.QueryState.FAILED; import static io.trino.execution.QueryState.FINISHED; import static io.trino.execution.QueryState.FINISHING; @@ -284,7 +284,7 @@ public void testQueryTypeBasedSelection() assertResourceGroup(queryRunner, newAdhocSession(), "SHOW TABLES", createResourceGroupId("global", "describe")); assertResourceGroup(queryRunner, newAdhocSession(), "EXPLAIN " + LONG_LASTING_QUERY, createResourceGroupId("global", "explain")); assertResourceGroup(queryRunner, newAdhocSession(), "DESCRIBE lineitem", createResourceGroupId("global", "describe")); - assertResourceGroup(queryRunner, newAdhocSession(), "RESET SESSION " + HASH_PARTITION_COUNT, createResourceGroupId("global", "data_definition")); + assertResourceGroup(queryRunner, newAdhocSession(), "RESET SESSION " + MAX_HASH_PARTITION_COUNT, createResourceGroupId("global", "data_definition")); } } diff --git a/testing/trino-tests/src/test/java/io/trino/security/TestingSystemSecurityMetadata.java b/testing/trino-tests/src/test/java/io/trino/security/TestingSystemSecurityMetadata.java index 2e554e14f788..8a039bdcb28c 100644 --- a/testing/trino-tests/src/test/java/io/trino/security/TestingSystemSecurityMetadata.java +++ b/testing/trino-tests/src/test/java/io/trino/security/TestingSystemSecurityMetadata.java @@ -259,4 +259,13 @@ public void tableRenamed(Session session, CatalogSchemaTableName sourceTable, Ca @Override public void tableDropped(Session session, CatalogSchemaTableName table) {} + + @Override + public void columnCreated(Session session, CatalogSchemaTableName table, String column) {} + + @Override + public void columnRenamed(Session session, CatalogSchemaTableName table, String oldColumn, String newColumn) {} + + @Override + public void columnDropped(Session session, CatalogSchemaTableName table, String column) {} } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestServer.java b/testing/trino-tests/src/test/java/io/trino/tests/TestServer.java index 99b162657aae..db624e999fda 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestServer.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestServer.java @@ -64,8 +64,8 @@ import static io.airlift.http.client.StatusResponseHandler.createStatusResponseHandler; import static io.airlift.json.JsonCodec.jsonCodec; import static io.airlift.testing.Closeables.closeAll; -import static io.trino.SystemSessionProperties.HASH_PARTITION_COUNT; import static io.trino.SystemSessionProperties.JOIN_DISTRIBUTION_TYPE; +import static io.trino.SystemSessionProperties.MAX_HASH_PARTITION_COUNT; import static io.trino.SystemSessionProperties.QUERY_MAX_MEMORY; import static io.trino.client.ClientCapabilities.PATH; import static io.trino.client.ProtocolHeaders.TRINO_HEADERS; @@ -188,7 +188,7 @@ public void testQuery() .setHeader(TRINO_HEADERS.requestPath(), "path") .setHeader(TRINO_HEADERS.requestClientInfo(), "{\"clientVersion\":\"testVersion\"}") .addHeader(TRINO_HEADERS.requestSession(), QUERY_MAX_MEMORY + "=1GB") - .addHeader(TRINO_HEADERS.requestSession(), JOIN_DISTRIBUTION_TYPE + "=partitioned," + HASH_PARTITION_COUNT + " = 43") + .addHeader(TRINO_HEADERS.requestSession(), JOIN_DISTRIBUTION_TYPE + "=partitioned," + MAX_HASH_PARTITION_COUNT + " = 43") .addHeader(TRINO_HEADERS.requestPreparedStatement(), "foo=select * from bar")) .map(JsonResponse::getValue) .peek(result -> assertNull(result.getError())) @@ -206,7 +206,7 @@ public void testQuery() assertEquals(queryInfo.getSession().getSystemProperties(), ImmutableMap.builder() .put(QUERY_MAX_MEMORY, "1GB") .put(JOIN_DISTRIBUTION_TYPE, "partitioned") - .put(HASH_PARTITION_COUNT, "43") + .put(MAX_HASH_PARTITION_COUNT, "43") .buildOrThrow()); // verify client info in session