diff --git a/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 b/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 index e03d57ba7e2a..1fa023c7fe2d 100644 --- a/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 +++ b/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 @@ -87,6 +87,10 @@ statement RENAME COLUMN (IF EXISTS)? from=qualifiedName TO to=identifier #renameColumn | ALTER TABLE (IF EXISTS)? tableName=qualifiedName DROP COLUMN (IF EXISTS)? column=qualifiedName #dropColumn + | ALTER TABLE (IF EXISTS)? tableName=qualifiedName + ALTER COLUMN columnName=qualifiedName SET DEFAULT literal #setDefaultValue + | ALTER TABLE (IF EXISTS)? tableName=qualifiedName + ALTER COLUMN columnName=qualifiedName DROP DEFAULT #dropDefaultValue | ALTER TABLE (IF EXISTS)? tableName=qualifiedName ALTER COLUMN columnName=qualifiedName SET DATA TYPE type #setColumnType | ALTER TABLE (IF EXISTS)? tableName=qualifiedName diff --git a/core/trino-main/src/main/java/io/trino/execution/DropDefaultValueTask.java b/core/trino-main/src/main/java/io/trino/execution/DropDefaultValueTask.java new file mode 100644 index 000000000000..2062ce9c3768 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/execution/DropDefaultValueTask.java @@ -0,0 +1,115 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.execution; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.inject.Inject; +import io.trino.Session; +import io.trino.connector.CatalogHandle; +import io.trino.execution.warnings.WarningCollector; +import io.trino.metadata.Metadata; +import io.trino.metadata.QualifiedObjectName; +import io.trino.metadata.RedirectionAwareTableHandle; +import io.trino.metadata.TableHandle; +import io.trino.security.AccessControl; +import io.trino.spi.connector.ColumnHandle; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.sql.tree.DropDefaultValue; +import io.trino.sql.tree.Expression; +import io.trino.sql.tree.QualifiedName; + +import java.util.List; + +import static com.google.common.util.concurrent.Futures.immediateVoidFuture; +import static io.trino.metadata.MetadataUtil.createQualifiedObjectName; +import static io.trino.spi.StandardErrorCode.COLUMN_NOT_FOUND; +import static io.trino.spi.StandardErrorCode.GENERIC_USER_ERROR; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; +import static io.trino.spi.connector.ConnectorCapabilities.DEFAULT_COLUMN_VALUE; +import static io.trino.sql.analyzer.SemanticExceptions.semanticException; +import static java.util.Objects.requireNonNull; + +public class DropDefaultValueTask + implements DataDefinitionTask +{ + private final Metadata metadata; + private final AccessControl accessControl; + + @Inject + public DropDefaultValueTask(Metadata metadata, AccessControl accessControl) + { + this.metadata = requireNonNull(metadata, "metadata is null"); + this.accessControl = requireNonNull(accessControl, "accessControl is null"); + } + + @Override + public String getName() + { + return "DROP DEFAULT"; + } + + @Override + public ListenableFuture execute( + DropDefaultValue statement, + QueryStateMachine stateMachine, + List parameters, + WarningCollector warningCollector) + { + Session session = stateMachine.getSession(); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); + RedirectionAwareTableHandle redirectionAwareTableHandle = metadata.getRedirectionAwareTableHandle(session, tableName); + if (redirectionAwareTableHandle.tableHandle().isEmpty()) { + String exceptionMessage = "Table '%s' does not exist".formatted(tableName); + if (metadata.getMaterializedView(session, tableName).isPresent()) { + exceptionMessage += ", but a materialized view with that name exists."; + } + else if (metadata.isView(session, tableName)) { + exceptionMessage += ", but a view with that name exists."; + } + if (!statement.isTableExists()) { + throw semanticException(TABLE_NOT_FOUND, statement, "%s", exceptionMessage); + } + return immediateVoidFuture(); + } + accessControl.checkCanAlterColumn(session.toSecurityContext(), tableName); + + TableHandle tableHandle = redirectionAwareTableHandle.tableHandle().get(); + CatalogHandle catalogHandle = tableHandle.catalogHandle(); + QualifiedName field = statement.getColumnName(); + if (field.getOriginalParts().size() != 1) { + throw semanticException(NOT_SUPPORTED, statement, "Cannot modify nested fields"); + } + String columnName = field.getOriginalParts().getFirst().getValue(); + ColumnHandle columnHandle = metadata.getColumnHandles(session, tableHandle).get(columnName); + + if (columnHandle == null) { + throw semanticException(COLUMN_NOT_FOUND, statement, "Column '%s' does not exist", columnName); + } + + ColumnMetadata columnMetadata = metadata.getColumnMetadata(session, tableHandle, columnHandle); + if (columnMetadata.isHidden()) { + throw semanticException(NOT_SUPPORTED, statement, "Cannot modify hidden column"); + } + if (columnMetadata.getDefaultValue().isEmpty()) { + throw semanticException(GENERIC_USER_ERROR, statement, "Column '%s' does not have a default value", columnName); + } + if (!metadata.getConnectorCapabilities(session, catalogHandle).contains(DEFAULT_COLUMN_VALUE)) { + throw semanticException(NOT_SUPPORTED, statement, "Catalog '%s' does not support default value for column name '%s'", catalogHandle, columnName); + } + + metadata.dropDefaultValue(session, tableHandle, columnHandle); + return immediateVoidFuture(); + } +} diff --git a/core/trino-main/src/main/java/io/trino/execution/SetDefaultValueTask.java b/core/trino-main/src/main/java/io/trino/execution/SetDefaultValueTask.java new file mode 100644 index 000000000000..83610b9916e7 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/execution/SetDefaultValueTask.java @@ -0,0 +1,124 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.execution; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.inject.Inject; +import io.trino.Session; +import io.trino.connector.CatalogHandle; +import io.trino.execution.warnings.WarningCollector; +import io.trino.metadata.Metadata; +import io.trino.metadata.QualifiedObjectName; +import io.trino.metadata.RedirectionAwareTableHandle; +import io.trino.metadata.TableHandle; +import io.trino.security.AccessControl; +import io.trino.spi.connector.ColumnHandle; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.sql.PlannerContext; +import io.trino.sql.tree.Expression; +import io.trino.sql.tree.NodeRef; +import io.trino.sql.tree.Parameter; +import io.trino.sql.tree.QualifiedName; +import io.trino.sql.tree.SetDefaultValue; + +import java.util.List; +import java.util.Map; + +import static com.google.common.util.concurrent.Futures.immediateVoidFuture; +import static io.trino.execution.ParameterExtractor.bindParameters; +import static io.trino.metadata.MetadataUtil.createQualifiedObjectName; +import static io.trino.spi.StandardErrorCode.COLUMN_NOT_FOUND; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; +import static io.trino.spi.connector.ConnectorCapabilities.DEFAULT_COLUMN_VALUE; +import static io.trino.sql.analyzer.ExpressionAnalyzer.analyzeDefaultColumnValue; +import static io.trino.sql.analyzer.SemanticExceptions.semanticException; +import static java.util.Objects.requireNonNull; + +public class SetDefaultValueTask + implements DataDefinitionTask +{ + private final PlannerContext plannerContext; + private final Metadata metadata; + private final AccessControl accessControl; + + @Inject + public SetDefaultValueTask(PlannerContext plannerContext, AccessControl accessControl) + { + this.plannerContext = requireNonNull(plannerContext, "plannerContext is null"); + this.metadata = plannerContext.getMetadata(); + this.accessControl = requireNonNull(accessControl, "accessControl is null"); + } + + @Override + public String getName() + { + return "SET DEFAULT"; + } + + @Override + public ListenableFuture execute( + SetDefaultValue statement, + QueryStateMachine stateMachine, + List parameters, + WarningCollector warningCollector) + { + Session session = stateMachine.getSession(); + Map, Expression> parameterLookup = bindParameters(statement, parameters); + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); + RedirectionAwareTableHandle redirectionAwareTableHandle = metadata.getRedirectionAwareTableHandle(session, tableName); + + if (redirectionAwareTableHandle.tableHandle().isEmpty()) { + String exceptionMessage = "Table '%s' does not exist".formatted(tableName); + if (metadata.getMaterializedView(session, tableName).isPresent()) { + exceptionMessage += ", but a materialized view with that name exists."; + } + else if (metadata.isView(session, tableName)) { + exceptionMessage += ", but a view with that name exists."; + } + if (!statement.isTableExists()) { + throw semanticException(TABLE_NOT_FOUND, statement, "%s", exceptionMessage); + } + return immediateVoidFuture(); + } + accessControl.checkCanAlterColumn(session.toSecurityContext(), tableName); + + TableHandle tableHandle = redirectionAwareTableHandle.tableHandle().get(); + CatalogHandle catalogHandle = tableHandle.catalogHandle(); + QualifiedName field = statement.getColumnName(); + if (field.getOriginalParts().size() != 1) { + throw semanticException(NOT_SUPPORTED, statement, "Cannot modify nested fields"); + } + String columnName = field.getOriginalParts().getFirst().getValue(); + ColumnHandle columnHandle = metadata.getColumnHandles(session, tableHandle).get(columnName); + + if (columnHandle == null) { + throw semanticException(COLUMN_NOT_FOUND, statement, "Column '%s' does not exist", columnName); + } + + ColumnMetadata columnMetadata = metadata.getColumnMetadata(session, tableHandle, columnHandle); + if (columnMetadata.isHidden()) { + throw semanticException(NOT_SUPPORTED, statement, "Cannot modify hidden column"); + } + + if (!metadata.getConnectorCapabilities(session, catalogHandle).contains(DEFAULT_COLUMN_VALUE)) { + throw semanticException(NOT_SUPPORTED, statement, "Catalog '%s' does not support default value for column name '%s'", catalogHandle, columnName); + } + Expression defaultValue = statement.getDefaultValue(); + analyzeDefaultColumnValue(session, plannerContext, accessControl, parameterLookup, warningCollector, columnMetadata.getType(), defaultValue); + + metadata.setDefaultValue(session, tableHandle, columnHandle, defaultValue.toString()); + return immediateVoidFuture(); + } +} diff --git a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java index d5732b0f7b9a..a62979dfd513 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java +++ b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java @@ -300,6 +300,16 @@ Optional getTableHandleForExecute( */ void addField(Session session, TableHandle tableHandle, List parentPath, String fieldName, Type type, boolean ignoreExisting); + /** + * Set the specified default value to the column. + */ + void setDefaultValue(Session session, TableHandle tableHandle, ColumnHandle column, String defaultValue); + + /** + * Drop a default value on the specified column. + */ + void dropDefaultValue(Session session, TableHandle tableHandle, ColumnHandle column); + /** * Set the specified type to the column. */ 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 27fdb632f65e..a3c272ec9c6c 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 @@ -1001,6 +1001,22 @@ public void dropField(Session session, TableHandle tableHandle, ColumnHandle col metadata.dropField(session.toConnectorSession(catalogHandle), tableHandle.connectorHandle(), column, fieldPath); } + @Override + public void setDefaultValue(Session session, TableHandle tableHandle, ColumnHandle column, String defaultValue) + { + CatalogHandle catalogHandle = tableHandle.catalogHandle(); + ConnectorMetadata metadata = getMetadataForWrite(session, catalogHandle); + metadata.setDefaultValue(session.toConnectorSession(catalogHandle), tableHandle.connectorHandle(), column, defaultValue); + } + + @Override + public void dropDefaultValue(Session session, TableHandle tableHandle, ColumnHandle column) + { + CatalogHandle catalogHandle = tableHandle.catalogHandle(); + ConnectorMetadata metadata = getMetadataForWrite(session, catalogHandle); + metadata.dropDefaultValue(session.toConnectorSession(catalogHandle), tableHandle.connectorHandle(), column); + } + @Override public void setColumnType(Session session, TableHandle tableHandle, ColumnHandle column, Type type) { diff --git a/core/trino-main/src/main/java/io/trino/server/QueryExecutionFactoryModule.java b/core/trino-main/src/main/java/io/trino/server/QueryExecutionFactoryModule.java index 4db7cf53f037..c9b78e209c2c 100644 --- a/core/trino-main/src/main/java/io/trino/server/QueryExecutionFactoryModule.java +++ b/core/trino-main/src/main/java/io/trino/server/QueryExecutionFactoryModule.java @@ -37,6 +37,7 @@ import io.trino.execution.DropBranchTask; import io.trino.execution.DropCatalogTask; import io.trino.execution.DropColumnTask; +import io.trino.execution.DropDefaultValueTask; import io.trino.execution.DropFunctionTask; import io.trino.execution.DropMaterializedViewTask; import io.trino.execution.DropNotNullConstraintTask; @@ -62,6 +63,7 @@ import io.trino.execution.RollbackTask; import io.trino.execution.SetAuthorizationTask; import io.trino.execution.SetColumnTypeTask; +import io.trino.execution.SetDefaultValueTask; import io.trino.execution.SetPathTask; import io.trino.execution.SetPropertiesTask; import io.trino.execution.SetRoleTask; @@ -89,6 +91,7 @@ import io.trino.sql.tree.DropBranch; import io.trino.sql.tree.DropCatalog; import io.trino.sql.tree.DropColumn; +import io.trino.sql.tree.DropDefaultValue; import io.trino.sql.tree.DropFunction; import io.trino.sql.tree.DropMaterializedView; import io.trino.sql.tree.DropNotNullConstraint; @@ -113,6 +116,7 @@ import io.trino.sql.tree.Rollback; import io.trino.sql.tree.SetAuthorizationStatement; import io.trino.sql.tree.SetColumnType; +import io.trino.sql.tree.SetDefaultValue; import io.trino.sql.tree.SetPath; import io.trino.sql.tree.SetProperties; import io.trino.sql.tree.SetRole; @@ -183,6 +187,8 @@ public void configure(Binder binder) bindDataDefinitionTask(binder, executionBinder, Revoke.class, RevokeTask.class); bindDataDefinitionTask(binder, executionBinder, RevokeRoles.class, RevokeRolesTask.class); bindDataDefinitionTask(binder, executionBinder, Rollback.class, RollbackTask.class); + bindDataDefinitionTask(binder, executionBinder, SetDefaultValue.class, SetDefaultValueTask.class); + bindDataDefinitionTask(binder, executionBinder, DropDefaultValue.class, DropDefaultValueTask.class); bindDataDefinitionTask(binder, executionBinder, SetColumnType.class, SetColumnTypeTask.class); bindDataDefinitionTask(binder, executionBinder, DropNotNullConstraint.class, DropNotNullConstraintTask.class); bindDataDefinitionTask(binder, executionBinder, SetPath.class, SetPathTask.class); 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 4138196025c8..cdcaa4387a3f 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 @@ -148,6 +148,7 @@ import io.trino.sql.tree.DereferenceExpression; import io.trino.sql.tree.DropCatalog; import io.trino.sql.tree.DropColumn; +import io.trino.sql.tree.DropDefaultValue; import io.trino.sql.tree.DropMaterializedView; import io.trino.sql.tree.DropNotNullConstraint; import io.trino.sql.tree.DropSchema; @@ -234,6 +235,7 @@ import io.trino.sql.tree.SelectItem; import io.trino.sql.tree.SetAuthorizationStatement; import io.trino.sql.tree.SetColumnType; +import io.trino.sql.tree.SetDefaultValue; import io.trino.sql.tree.SetOperation; import io.trino.sql.tree.SetProperties; import io.trino.sql.tree.SetSession; @@ -1125,6 +1127,18 @@ protected Scope visitAddColumn(AddColumn node, Optional scope) return createAndAssignScope(node, scope); } + @Override + protected Scope visitSetDefaultValue(SetDefaultValue node, Optional scope) + { + return createAndAssignScope(node, scope); + } + + @Override + protected Scope visitDropDefaultValue(DropDefaultValue node, Optional scope) + { + return createAndAssignScope(node, scope); + } + @Override protected Scope visitSetColumnType(SetColumnType node, Optional scope) { 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 1f510f49cdf6..b0377ee376cd 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 @@ -371,6 +371,28 @@ public void addColumn(ConnectorSession session, ConnectorTableHandle tableHandle tables.put(tableName, new ConnectorTableMetadata(tableName, columns.build(), tableMetadata.getProperties(), tableMetadata.getComment())); } + @Override + public void setDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, String defaultValue) + { + ConnectorTableMetadata tableMetadata = getTableMetadata(session, tableHandle); + SchemaTableName tableName = getTableName(tableHandle); + List columns = new ArrayList<>(tableMetadata.getColumns()); + ColumnMetadata columnMetadata = getColumnMetadata(session, tableHandle, column); + columns.set(columns.indexOf(columnMetadata), ColumnMetadata.builderFrom(columnMetadata).setDefaultValue(Optional.of(defaultValue)).build()); + tables.put(tableName, new ConnectorTableMetadata(tableName, ImmutableList.copyOf(columns), tableMetadata.getProperties(), tableMetadata.getComment())); + } + + @Override + public void dropDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle) + { + ConnectorTableMetadata tableMetadata = getTableMetadata(session, tableHandle); + SchemaTableName tableName = getTableName(tableHandle); + List columns = new ArrayList<>(tableMetadata.getColumns()); + ColumnMetadata columnMetadata = getColumnMetadata(session, tableHandle, columnHandle); + columns.set(columns.indexOf(columnMetadata), ColumnMetadata.builderFrom(columnMetadata).setDefaultValue(Optional.empty()).build()); + tables.put(tableName, new ConnectorTableMetadata(tableName, ImmutableList.copyOf(columns), tableMetadata.getProperties(), tableMetadata.getComment())); + } + @Override public void setColumnType(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, Type type) { diff --git a/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java b/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java index d3276e330a31..e9f97d0c7599 100644 --- a/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java +++ b/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java @@ -488,6 +488,24 @@ public void addField(ConnectorSession session, ConnectorTableHandle tableHandle, } } + @Override + public void setDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, String defaultValue) + { + Span span = startSpan("setDefaultValue", tableHandle); + try (var _ = scopedSpan(span)) { + delegate.setDefaultValue(session, tableHandle, column, defaultValue); + } + } + + @Override + public void dropDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle) + { + Span span = startSpan("dropDefaultValue", tableHandle); + try (var _ = scopedSpan(span)) { + delegate.dropDefaultValue(session, tableHandle, columnHandle); + } + } + @Override public void setColumnType(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, Type type) { diff --git a/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java b/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java index a5423a180cb6..b5182a6fd952 100644 --- a/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java +++ b/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java @@ -527,6 +527,24 @@ public void addField(Session session, TableHandle tableHandle, List pare } } + @Override + public void setDefaultValue(Session session, TableHandle tableHandle, ColumnHandle column, String defaultValue) + { + Span span = startSpan("setDefaultValue", tableHandle); + try (var _ = scopedSpan(span)) { + delegate.setDefaultValue(session, tableHandle, column, defaultValue); + } + } + + @Override + public void dropDefaultValue(Session session, TableHandle tableHandle, ColumnHandle column) + { + Span span = startSpan("dropDefaultValue", tableHandle); + try (var _ = scopedSpan(span)) { + delegate.dropDefaultValue(session, tableHandle, column); + } + } + @Override public void setColumnType(Session session, TableHandle tableHandle, ColumnHandle column, Type type) { diff --git a/core/trino-main/src/main/java/io/trino/util/StatementUtils.java b/core/trino-main/src/main/java/io/trino/util/StatementUtils.java index ff4256ce0b96..e18200c6672f 100644 --- a/core/trino-main/src/main/java/io/trino/util/StatementUtils.java +++ b/core/trino-main/src/main/java/io/trino/util/StatementUtils.java @@ -33,6 +33,7 @@ import io.trino.execution.DropBranchTask; import io.trino.execution.DropCatalogTask; import io.trino.execution.DropColumnTask; +import io.trino.execution.DropDefaultValueTask; import io.trino.execution.DropFunctionTask; import io.trino.execution.DropMaterializedViewTask; import io.trino.execution.DropNotNullConstraintTask; @@ -57,6 +58,7 @@ import io.trino.execution.RollbackTask; import io.trino.execution.SetAuthorizationTask; import io.trino.execution.SetColumnTypeTask; +import io.trino.execution.SetDefaultValueTask; import io.trino.execution.SetPathTask; import io.trino.execution.SetPropertiesTask; import io.trino.execution.SetRoleTask; @@ -89,6 +91,7 @@ import io.trino.sql.tree.DropBranch; import io.trino.sql.tree.DropCatalog; import io.trino.sql.tree.DropColumn; +import io.trino.sql.tree.DropDefaultValue; import io.trino.sql.tree.DropFunction; import io.trino.sql.tree.DropMaterializedView; import io.trino.sql.tree.DropNotNullConstraint; @@ -119,6 +122,7 @@ import io.trino.sql.tree.Rollback; import io.trino.sql.tree.SetAuthorizationStatement; import io.trino.sql.tree.SetColumnType; +import io.trino.sql.tree.SetDefaultValue; import io.trino.sql.tree.SetPath; import io.trino.sql.tree.SetProperties; import io.trino.sql.tree.SetRole; @@ -242,6 +246,8 @@ private StatementUtils() {} .add(dataDefinitionStatement(Revoke.class, RevokeTask.class)) .add(dataDefinitionStatement(RevokeRoles.class, RevokeRolesTask.class)) .add(dataDefinitionStatement(Rollback.class, RollbackTask.class)) + .add(dataDefinitionStatement(SetDefaultValue.class, SetDefaultValueTask.class)) + .add(dataDefinitionStatement(DropDefaultValue.class, DropDefaultValueTask.class)) .add(dataDefinitionStatement(SetColumnType.class, SetColumnTypeTask.class)) .add(dataDefinitionStatement(DropNotNullConstraint.class, DropNotNullConstraintTask.class)) .add(dataDefinitionStatement(SetPath.class, SetPathTask.class)) 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 e2d19b019958..b2fa479a0d93 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 @@ -658,6 +658,18 @@ public void setColumnComment(ConnectorSession session, ConnectorTableHandle tabl @Override public void addColumn(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnMetadata column, ColumnPosition position) {} + @Override + public void setDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, String defaultValue) + { + throw new UnsupportedOperationException(); + } + + @Override + public void dropDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle) + { + throw new UnsupportedOperationException(); + } + @Override public void setColumnType(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, Type type) {} diff --git a/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java b/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java index f30f58a96c95..03947e3e9172 100644 --- a/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java +++ b/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java @@ -435,6 +435,44 @@ public void renameColumn(Session session, TableHandle tableHandle, CatalogSchema tables.put(tableName, new MockConnectorTableMetadata(new ConnectorTableMetadata(tableName, columns))); } + @Override + public void setDefaultValue(Session session, TableHandle tableHandle, ColumnHandle columnHandle, String defaultValue) + { + SchemaTableName tableName = getTableName(tableHandle); + ConnectorTableMetadata metadata = tables.get(tableName).metadata; + + ImmutableList.Builder columns = ImmutableList.builderWithExpectedSize(metadata.getColumns().size()); + for (ColumnMetadata column : metadata.getColumns()) { + if (column.getName().equals(((TestingColumnHandle) columnHandle).getName())) { + columns.add(ColumnMetadata.builderFrom(column).setDefaultValue(Optional.of(defaultValue)).build()); + } + else { + columns.add(column); + } + } + + tables.put(tableName, new MockConnectorTableMetadata(new ConnectorTableMetadata(tableName, columns.build()))); + } + + @Override + public void dropDefaultValue(Session session, TableHandle tableHandle, ColumnHandle columnHandle) + { + SchemaTableName tableName = getTableName(tableHandle); + ConnectorTableMetadata metadata = tables.get(tableName).metadata; + + ImmutableList.Builder columns = ImmutableList.builderWithExpectedSize(metadata.getColumns().size()); + for (ColumnMetadata column : metadata.getColumns()) { + if (column.getName().equals(((TestingColumnHandle) columnHandle).getName())) { + columns.add(ColumnMetadata.builderFrom(column).setDefaultValue(Optional.empty()).build()); + } + else { + columns.add(column); + } + } + + tables.put(tableName, new MockConnectorTableMetadata(new ConnectorTableMetadata(tableName, columns.build()))); + } + @Override public void setColumnType(Session session, TableHandle tableHandle, ColumnHandle columnHandle, Type type) { diff --git a/core/trino-main/src/test/java/io/trino/execution/TestDropDefaultValueTask.java b/core/trino-main/src/test/java/io/trino/execution/TestDropDefaultValueTask.java new file mode 100644 index 000000000000..e01754d10b6b --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/execution/TestDropDefaultValueTask.java @@ -0,0 +1,196 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.execution; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.ListenableFuture; +import io.trino.Session; +import io.trino.connector.CatalogHandle; +import io.trino.execution.warnings.WarningCollector; +import io.trino.metadata.QualifiedObjectName; +import io.trino.metadata.TableHandle; +import io.trino.security.AllowAllAccessControl; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.connector.ConnectorCapabilities; +import io.trino.spi.connector.ConnectorTableMetadata; +import io.trino.sql.tree.DropDefaultValue; +import io.trino.sql.tree.NodeLocation; +import io.trino.sql.tree.QualifiedName; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.Set; + +import static io.airlift.concurrent.MoreFutures.getFutureValue; +import static io.trino.spi.StandardErrorCode.COLUMN_NOT_FOUND; +import static io.trino.spi.StandardErrorCode.GENERIC_USER_ERROR; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; +import static io.trino.spi.connector.ConnectorCapabilities.DEFAULT_COLUMN_VALUE; +import static io.trino.spi.connector.SaveMode.FAIL; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.sql.planner.TestingPlannerContext.plannerContextBuilder; +import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; +import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; +import static org.assertj.core.api.Assertions.assertThat; + +final class TestDropDefaultValueTask + extends BaseDataDefinitionTaskTest +{ + @Override + @BeforeEach + public void setUp() + { + super.setUp(); + metadata = new MockMetadataWithDefaultValue(TEST_CATALOG_NAME); + plannerContext = plannerContextBuilder().withMetadata(metadata).build(); + } + + @Test + void testDropDefaultValue() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + + metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTableWithDefault(tableName), FAIL); + TableHandle table = metadata.getTableHandle(testSession, tableName).orElseThrow(); + assertThat(metadata.getTableMetadata(testSession, table).columns()) + .containsExactly(column("a", "123"), column("b", "123")); + + getFutureValue(executeDropDefaultValue(asQualifiedName(tableName), "b", false)); + assertThat(metadata.getTableMetadata(testSession, table).columns()) + .containsExactly(column("a", "123"), column("b", null)); + } + + @Test + void testDropDefaultValueNotExistingTable() + { + QualifiedObjectName tableName = qualifiedObjectName("not_existing_table"); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeDropDefaultValue(asQualifiedName(tableName), "b", false))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessageContaining("Table '%s' does not exist", tableName); + } + + @Test + void testDropDefaultValueNotExistingTableIfExists() + { + QualifiedName tableName = qualifiedName("not_existing_table"); + + getFutureValue(executeDropDefaultValue(tableName, "b", true)); + // no exception + } + + @Test + void testDropDefaultFieldValue() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTableWithDefault(tableName), FAIL); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeDropDefaultValue(asQualifiedName(tableName), "missing_column", false))) + .hasErrorCode(COLUMN_NOT_FOUND) + .hasMessageContaining("Column 'missing_column' does not exist"); + } + + @Test + void testDropDefaultValueMissingColumn() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTableWithDefault(tableName), FAIL); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeDropDefaultValue(asQualifiedName(tableName), QualifiedName.of("row", "field"), false))) + .hasErrorCode(NOT_SUPPORTED) + .hasMessageContaining("Cannot modify nested fields"); + } + + @Test + void testDropNonDefaultValueColumn() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + ConnectorTableMetadata tableMetadata = new ConnectorTableMetadata(tableName.asSchemaTableName(), ImmutableList.of(column("a", null))); + metadata.createTable(testSession, TEST_CATALOG_NAME, tableMetadata, FAIL); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeDropDefaultValue(asQualifiedName(tableName), "a", false))) + .hasErrorCode(GENERIC_USER_ERROR) + .hasMessageContaining("Column 'a' does not have a default value"); + } + + @Test + void testDropDefaultValueOnView() + { + QualifiedObjectName viewName = qualifiedObjectName("existing_view"); + metadata.createView(testSession, viewName, someView(), ImmutableMap.of(), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeDropDefaultValue(asQualifiedName(viewName), "test", false))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessageContaining("Table '%s' does not exist, but a view with that name exists.", viewName); + } + + @Test + void testDropDefaultValueOnMaterializedView() + { + QualifiedObjectName materializedViewName = qualifiedObjectName("existing_materialized_view"); + metadata.createMaterializedView(testSession, QualifiedObjectName.valueOf(materializedViewName.toString()), someMaterializedView(), MATERIALIZED_VIEW_PROPERTIES, false, false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeDropDefaultValue(asQualifiedName(materializedViewName), "test", false))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessageContaining("Table '%s' does not exist, but a materialized view with that name exists.", materializedViewName); + } + + private ListenableFuture executeDropDefaultValue(QualifiedName table, String column, boolean tableExists) + { + return executeDropDefaultValue(table, QualifiedName.of(column), tableExists); + } + + private ListenableFuture executeDropDefaultValue(QualifiedName table, QualifiedName column, boolean tableExists) + { + return new DropDefaultValueTask(plannerContext.getMetadata(), new AllowAllAccessControl()) + .execute(new DropDefaultValue( + new NodeLocation(1, 1), + table, + column, + tableExists), queryStateMachine, ImmutableList.of(), WarningCollector.NOOP); + } + + private static ConnectorTableMetadata simpleTableWithDefault(QualifiedObjectName tableName) + { + return new ConnectorTableMetadata(tableName.asSchemaTableName(), ImmutableList.of(column("a", "123"), column("b", "123"))); + } + + private static ColumnMetadata column(String name, String defaultValue) + { + return ColumnMetadata.builder() + .setName(name) + .setType(BIGINT) + .setDefaultValue(Optional.ofNullable(defaultValue)) + .build(); + } + + private static class MockMetadataWithDefaultValue + extends MockMetadata + { + public MockMetadataWithDefaultValue(String catalogName) + { + super(catalogName); + } + + @Override + public Set getConnectorCapabilities(Session session, CatalogHandle catalogHandle) + { + return ImmutableSet.of(DEFAULT_COLUMN_VALUE); + } + } +} diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetDefaultValueTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetDefaultValueTask.java new file mode 100644 index 000000000000..e9055a5170e8 --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetDefaultValueTask.java @@ -0,0 +1,214 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.execution; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.ListenableFuture; +import io.trino.Session; +import io.trino.connector.CatalogHandle; +import io.trino.execution.warnings.WarningCollector; +import io.trino.metadata.QualifiedObjectName; +import io.trino.metadata.TableHandle; +import io.trino.security.AllowAllAccessControl; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.connector.ConnectorCapabilities; +import io.trino.spi.connector.ConnectorTableMetadata; +import io.trino.sql.tree.Literal; +import io.trino.sql.tree.LongLiteral; +import io.trino.sql.tree.NodeLocation; +import io.trino.sql.tree.QualifiedName; +import io.trino.sql.tree.SetDefaultValue; +import io.trino.sql.tree.StringLiteral; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.Set; + +import static io.airlift.concurrent.MoreFutures.getFutureValue; +import static io.trino.spi.StandardErrorCode.COLUMN_NOT_FOUND; +import static io.trino.spi.StandardErrorCode.INVALID_DEFAULT_COLUMN_VALUE; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; +import static io.trino.spi.connector.ConnectorCapabilities.DEFAULT_COLUMN_VALUE; +import static io.trino.spi.connector.SaveMode.FAIL; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.sql.planner.TestingPlannerContext.plannerContextBuilder; +import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; +import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; +import static org.assertj.core.api.Assertions.assertThat; + +final class TestSetDefaultValueTask + extends BaseDataDefinitionTaskTest +{ + @Override + @BeforeEach + public void setUp() + { + super.setUp(); + metadata = new MockMetadataWithDefaultValue(TEST_CATALOG_NAME); + plannerContext = plannerContextBuilder().withMetadata(metadata).build(); + } + + @Test + void testSetDefaultValue() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + + metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTable(tableName), FAIL); + TableHandle table = metadata.getTableHandle(testSession, tableName).orElseThrow(); + assertThat(metadata.getTableMetadata(testSession, table).columns()) + .containsExactly(column("a", null), column("b", null)); + + getFutureValue(executeSetDefaultValue(asQualifiedName(tableName), "b", "123", false)); + assertThat(metadata.getTableMetadata(testSession, table).columns()) + .containsExactly(column("a", null), column("b", "123")); + + getFutureValue(executeSetDefaultValue(asQualifiedName(tableName), "b", "456", false)); + assertThat(metadata.getTableMetadata(testSession, table).columns()) + .containsExactly(column("a", null), column("b", "456")); + } + + @Test + void testSetDefaultValueNotExistingTable() + { + QualifiedObjectName tableName = qualifiedObjectName("not_existing_table"); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeSetDefaultValue(asQualifiedName(tableName), "b", "123", false))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessageContaining("Table '%s' does not exist", tableName); + } + + @Test + void testSetDefaultValueNotExistingTableIfExists() + { + QualifiedName tableName = qualifiedName("not_existing_table"); + + getFutureValue(executeSetDefaultValue(tableName, "b", "123", true)); + // no exception + } + + @Test + void testSetWrongDefaultValueType() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTable(tableName), FAIL); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeSetDefaultValue( + asQualifiedName(tableName), + QualifiedName.of("a"), + new StringLiteral(new NodeLocation(1, 1), "test"), + false))) + .hasErrorCode(INVALID_DEFAULT_COLUMN_VALUE) + .hasMessageContaining("line 1:1: ''test'' is not a valid BIGINT literal"); + } + + @Test + void testSetDefaultFieldValue() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTable(tableName), FAIL); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeSetDefaultValue(asQualifiedName(tableName), "missing_column", "123", false))) + .hasErrorCode(COLUMN_NOT_FOUND) + .hasMessageContaining("Column 'missing_column' does not exist"); + } + + @Test + void testSetDefaultValueMissingColumn() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTable(tableName), FAIL); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeSetDefaultValue( + asQualifiedName(tableName), + QualifiedName.of("row", "field"), + new LongLiteral(new NodeLocation(1, 1), "123"), + false))) + .hasErrorCode(NOT_SUPPORTED) + .hasMessageContaining("Cannot modify nested fields"); + } + + @Test + void testSetDefaultValueOnView() + { + QualifiedObjectName viewName = qualifiedObjectName("existing_view"); + metadata.createView(testSession, viewName, someView(), ImmutableMap.of(), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeSetDefaultValue(asQualifiedName(viewName), "test", "123", false))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessageContaining("Table '%s' does not exist, but a view with that name exists.", viewName); + } + + @Test + void testSetDefaultValueOnMaterializedView() + { + QualifiedObjectName materializedViewName = qualifiedObjectName("existing_materialized_view"); + metadata.createMaterializedView(testSession, QualifiedObjectName.valueOf(materializedViewName.toString()), someMaterializedView(), MATERIALIZED_VIEW_PROPERTIES, false, false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeSetDefaultValue(asQualifiedName(materializedViewName), "test", "123", false))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessageContaining("Table '%s' does not exist, but a materialized view with that name exists.", materializedViewName); + } + + private ListenableFuture executeSetDefaultValue(QualifiedName table, String column, String defaultValue, boolean tableExists) + { + return executeSetDefaultValue(table, QualifiedName.of(column), new LongLiteral(new NodeLocation(1, 1), defaultValue), tableExists); + } + + private ListenableFuture executeSetDefaultValue(QualifiedName table, QualifiedName column, Literal defaultValue, boolean tableExists) + { + return new SetDefaultValueTask(plannerContext, new AllowAllAccessControl()) + .execute(new SetDefaultValue( + new NodeLocation(1, 1), + table, + column, + defaultValue, + tableExists), + queryStateMachine, + ImmutableList.of(), + WarningCollector.NOOP); + } + + private static ConnectorTableMetadata simpleTable(QualifiedObjectName tableName) + { + return new ConnectorTableMetadata(tableName.asSchemaTableName(), ImmutableList.of(column("a", null), column("b", null))); + } + + private static ColumnMetadata column(String name, String defaultValue) + { + return ColumnMetadata.builder() + .setName(name) + .setType(BIGINT) + .setDefaultValue(Optional.ofNullable(defaultValue)) + .build(); + } + + private static class MockMetadataWithDefaultValue + extends MockMetadata + { + public MockMetadataWithDefaultValue(String catalogName) + { + super(catalogName); + } + + @Override + public Set getConnectorCapabilities(Session session, CatalogHandle catalogHandle) + { + return ImmutableSet.of(DEFAULT_COLUMN_VALUE); + } + } +} diff --git a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java index 448f95547c81..f395ade048f3 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java +++ b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java @@ -382,6 +382,18 @@ public void dropField(Session session, TableHandle tableHandle, ColumnHandle col throw new UnsupportedOperationException(); } + @Override + public void setDefaultValue(Session session, TableHandle tableHandle, ColumnHandle column, String defaultValue) + { + throw new UnsupportedOperationException(); + } + + @Override + public void dropDefaultValue(Session session, TableHandle tableHandle, ColumnHandle column) + { + throw new UnsupportedOperationException(); + } + @Override public void setColumnType(Session session, TableHandle tableHandle, ColumnHandle column, Type type) { diff --git a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java index 79f977fb24c8..61eafe152304 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java +++ b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java @@ -51,6 +51,7 @@ import io.trino.sql.tree.DropBranch; import io.trino.sql.tree.DropCatalog; import io.trino.sql.tree.DropColumn; +import io.trino.sql.tree.DropDefaultValue; import io.trino.sql.tree.DropFunction; import io.trino.sql.tree.DropMaterializedView; import io.trino.sql.tree.DropNotNullConstraint; @@ -147,6 +148,7 @@ import io.trino.sql.tree.SessionProperty; import io.trino.sql.tree.SetAuthorizationStatement; import io.trino.sql.tree.SetColumnType; +import io.trino.sql.tree.SetDefaultValue; import io.trino.sql.tree.SetPath; import io.trino.sql.tree.SetProperties; import io.trino.sql.tree.SetRole; @@ -1877,6 +1879,37 @@ protected Void visitAddColumn(AddColumn node, Integer indent) return null; } + @Override + protected Void visitSetDefaultValue(SetDefaultValue node, Integer context) + { + builder.append("ALTER TABLE "); + if (node.isTableExists()) { + builder.append("IF EXISTS "); + } + builder.append(formatName(node.getTableName())) + .append(" ALTER COLUMN ") + .append(formatName(node.getColumnName())) + .append(" SET DEFAULT ") + .append(formatExpression(node.getDefaultValue())); + + return null; + } + + @Override + protected Void visitDropDefaultValue(DropDefaultValue node, Integer context) + { + builder.append("ALTER TABLE "); + if (node.isTableExists()) { + builder.append("IF EXISTS "); + } + builder.append(formatName(node.getTableName())) + .append(" ALTER COLUMN ") + .append(formatName(node.getColumnName())) + .append(" DROP DEFAULT"); + + return null; + } + @Override protected Void visitSetColumnType(SetColumnType node, Integer context) { diff --git a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java index 6cdc3086b816..6c65ef8bb6f4 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java +++ b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java @@ -86,6 +86,7 @@ import io.trino.sql.tree.DropBranch; import io.trino.sql.tree.DropCatalog; import io.trino.sql.tree.DropColumn; +import io.trino.sql.tree.DropDefaultValue; import io.trino.sql.tree.DropFunction; import io.trino.sql.tree.DropMaterializedView; import io.trino.sql.tree.DropNotNullConstraint; @@ -251,6 +252,7 @@ import io.trino.sql.tree.SessionProperty; import io.trino.sql.tree.SetAuthorizationStatement; import io.trino.sql.tree.SetColumnType; +import io.trino.sql.tree.SetDefaultValue; import io.trino.sql.tree.SetPath; import io.trino.sql.tree.SetProperties; import io.trino.sql.tree.SetRole; @@ -870,6 +872,27 @@ private Optional toColumnPosition(SqlBaseParser.AddColumnContext return Optional.empty(); } + @Override + public Node visitSetDefaultValue(SqlBaseParser.SetDefaultValueContext context) + { + return new SetDefaultValue( + getLocation(context), + getQualifiedName(context.tableName), + getQualifiedName(context.columnName), + (Expression) visit(context.literal()), + context.EXISTS() != null); + } + + @Override + public Node visitDropDefaultValue(SqlBaseParser.DropDefaultValueContext context) + { + return new DropDefaultValue( + getLocation(context), + getQualifiedName(context.tableName), + getQualifiedName(context.columnName), + context.EXISTS() != null); + } + @Override public Node visitSetColumnType(SqlBaseParser.SetColumnTypeContext context) { diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java b/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java index 0619152ff97d..2bb0ce8c75ef 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java @@ -692,6 +692,16 @@ protected R visitAddColumn(AddColumn node, C context) return visitStatement(node, context); } + protected R visitSetDefaultValue(SetDefaultValue node, C context) + { + return visitStatement(node, context); + } + + protected R visitDropDefaultValue(DropDefaultValue node, C context) + { + return visitStatement(node, context); + } + protected R visitSetColumnType(SetColumnType node, C context) { return visitStatement(node, context); diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/DropDefaultValue.java b/core/trino-parser/src/main/java/io/trino/sql/tree/DropDefaultValue.java new file mode 100644 index 000000000000..3680fcdbca72 --- /dev/null +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/DropDefaultValue.java @@ -0,0 +1,96 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.sql.tree; + +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.util.Objects.requireNonNull; + +public class DropDefaultValue + extends Statement +{ + private final QualifiedName tableName; + private final QualifiedName columnName; + private final boolean tableExists; + + public DropDefaultValue(NodeLocation location, QualifiedName tableName, QualifiedName columnName, boolean tableExists) + { + super(location); + this.tableName = requireNonNull(tableName, "tableName is null"); + this.columnName = requireNonNull(columnName, "columnName is null"); + this.tableExists = tableExists; + } + + public QualifiedName getTableName() + { + return tableName; + } + + public QualifiedName getColumnName() + { + return columnName; + } + + public boolean isTableExists() + { + return tableExists; + } + + @Override + public R accept(AstVisitor visitor, C context) + { + return visitor.visitDropDefaultValue(this, context); + } + + @Override + public List getChildren() + { + return ImmutableList.of(); + } + + @Override + public int hashCode() + { + return Objects.hash(tableName, columnName, tableExists); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + DropDefaultValue o = (DropDefaultValue) obj; + return Objects.equals(tableName, o.tableName) && + Objects.equals(columnName, o.columnName) && + tableExists == o.tableExists; + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("name", tableName) + .add("column", columnName) + .add("tableExists", tableExists) + .toString(); + } +} diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/SetDefaultValue.java b/core/trino-parser/src/main/java/io/trino/sql/tree/SetDefaultValue.java new file mode 100644 index 000000000000..da3a286abc99 --- /dev/null +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/SetDefaultValue.java @@ -0,0 +1,105 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.sql.tree; + +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.util.Objects.requireNonNull; + +public class SetDefaultValue + extends Statement +{ + private final QualifiedName tableName; + private final QualifiedName columnName; + private final Expression defaultValue; + private final boolean tableExists; + + public SetDefaultValue(NodeLocation location, QualifiedName tableName, QualifiedName columnName, Expression defaultValue, boolean tableExists) + { + super(location); + this.tableName = requireNonNull(tableName, "tableName is null"); + this.columnName = requireNonNull(columnName, "columnName is null"); + this.defaultValue = requireNonNull(defaultValue, "defaultValue is null"); + this.tableExists = tableExists; + } + + public QualifiedName getTableName() + { + return tableName; + } + + public QualifiedName getColumnName() + { + return columnName; + } + + public Expression getDefaultValue() + { + return defaultValue; + } + + public boolean isTableExists() + { + return tableExists; + } + + @Override + public R accept(AstVisitor visitor, C context) + { + return visitor.visitSetDefaultValue(this, context); + } + + @Override + public List getChildren() + { + return ImmutableList.of(defaultValue); + } + + @Override + public int hashCode() + { + return Objects.hash(tableName, columnName, defaultValue, tableExists); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + SetDefaultValue o = (SetDefaultValue) obj; + return Objects.equals(tableName, o.tableName) && + Objects.equals(columnName, o.columnName) && + Objects.equals(defaultValue, o.defaultValue) && + tableExists == o.tableExists; + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("name", tableName) + .add("column", columnName) + .add("defaultValue", defaultValue) + .add("tableExists", tableExists) + .toString(); + } +} diff --git a/core/trino-parser/src/test/java/io/trino/sql/TestSqlFormatter.java b/core/trino-parser/src/test/java/io/trino/sql/TestSqlFormatter.java index 02399b7fdac0..1b21adfb4a4e 100644 --- a/core/trino-parser/src/test/java/io/trino/sql/TestSqlFormatter.java +++ b/core/trino-parser/src/test/java/io/trino/sql/TestSqlFormatter.java @@ -28,6 +28,7 @@ import io.trino.sql.tree.CreateView; import io.trino.sql.tree.Delete; import io.trino.sql.tree.DropBranch; +import io.trino.sql.tree.DropDefaultValue; import io.trino.sql.tree.ExecuteImmediate; import io.trino.sql.tree.FastForwardBranch; import io.trino.sql.tree.GenericDataType; @@ -41,6 +42,7 @@ import io.trino.sql.tree.QualifiedName; import io.trino.sql.tree.Query; import io.trino.sql.tree.RefreshView; +import io.trino.sql.tree.SetDefaultValue; import io.trino.sql.tree.ShowBranches; import io.trino.sql.tree.ShowCatalogs; import io.trino.sql.tree.ShowColumns; @@ -537,6 +539,52 @@ public void testAddColumn() .isEqualTo("ALTER TABLE foo.t ADD COLUMN c VARCHAR AFTER b"); } + @Test + public void testAlterColumnSetDefault() + { + assertThat(formatSql(new SetDefaultValue( + new NodeLocation(1, 1), + QualifiedName.of(ImmutableList.of( + new Identifier(new NodeLocation(1, 13), "foo", false), + new Identifier(new NodeLocation(1, 17), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(new NodeLocation(1, 32), "a", false))), + new LongLiteral(new NodeLocation(1, 46), "123"), + false))) + .isEqualTo("ALTER TABLE foo.t ALTER COLUMN a SET DEFAULT 123"); + + assertThat(formatSql(new SetDefaultValue( + new NodeLocation(1, 1), + QualifiedName.of(ImmutableList.of( + new Identifier(new NodeLocation(1, 23), "foo", false), + new Identifier(new NodeLocation(1, 27), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(new NodeLocation(1, 42), "b", false))), + new LongLiteral(new NodeLocation(1, 56), "123"), + true))) + .isEqualTo("ALTER TABLE IF EXISTS foo.t ALTER COLUMN b SET DEFAULT 123"); + } + + @Test + public void testAlterColumnDropDefault() + { + assertThat(formatSql(new DropDefaultValue( + new NodeLocation(1, 1), + QualifiedName.of(ImmutableList.of( + new Identifier(new NodeLocation(1, 13), "foo", false), + new Identifier(new NodeLocation(1, 17), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(new NodeLocation(1, 32), "a", false))), + false))) + .isEqualTo("ALTER TABLE foo.t ALTER COLUMN a DROP DEFAULT"); + + assertThat(formatSql(new DropDefaultValue( + new NodeLocation(1, 1), + QualifiedName.of(ImmutableList.of( + new Identifier(new NodeLocation(1, 23), "foo", false), + new Identifier(new NodeLocation(1, 27), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(new NodeLocation(1, 42), "b", false))), + true))) + .isEqualTo("ALTER TABLE IF EXISTS foo.t ALTER COLUMN b DROP DEFAULT"); + } + @Test public void testCommentOnTable() { diff --git a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java index 3a9118cd3d0d..6ad375c5b3a7 100644 --- a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java +++ b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java @@ -63,6 +63,7 @@ import io.trino.sql.tree.DropBranch; import io.trino.sql.tree.DropCatalog; import io.trino.sql.tree.DropColumn; +import io.trino.sql.tree.DropDefaultValue; import io.trino.sql.tree.DropMaterializedView; import io.trino.sql.tree.DropNotNullConstraint; import io.trino.sql.tree.DropRole; @@ -187,6 +188,7 @@ import io.trino.sql.tree.SessionProperty; import io.trino.sql.tree.SetAuthorizationStatement; import io.trino.sql.tree.SetColumnType; +import io.trino.sql.tree.SetDefaultValue; import io.trino.sql.tree.SetPath; import io.trino.sql.tree.SetProperties; import io.trino.sql.tree.SetRole; @@ -4179,6 +4181,52 @@ public void testDropColumn() false)); } + @Test + public void testAlterColumnSetDefault() + { + assertThat(statement("ALTER TABLE foo.t ALTER COLUMN a SET DEFAULT 123")) + .isEqualTo(new SetDefaultValue( + location(1, 1), + QualifiedName.of(ImmutableList.of( + new Identifier(location(1, 13), "foo", false), + new Identifier(location(1, 17), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(new NodeLocation(1, 32), "a", false))), + new LongLiteral(location(1, 46), "123"), + false)); + + assertThat(statement("ALTER TABLE IF EXISTS foo.t ALTER COLUMN b SET DEFAULT 123")) + .isEqualTo(new SetDefaultValue( + location(1, 1), + QualifiedName.of(ImmutableList.of( + new Identifier(location(1, 23), "foo", false), + new Identifier(location(1, 27), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(new NodeLocation(1, 42), "b", false))), + new LongLiteral(location(1, 56), "123"), + true)); + } + + @Test + public void testAlterColumnDropDefault() + { + assertThat(statement("ALTER TABLE foo.t ALTER COLUMN a DROP DEFAULT")) + .isEqualTo(new DropDefaultValue( + location(1, 1), + QualifiedName.of(ImmutableList.of( + new Identifier(location(1, 13), "foo", false), + new Identifier(location(1, 17), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(new NodeLocation(1, 32), "a", false))), + false)); + + assertThat(statement("ALTER TABLE IF EXISTS foo.t ALTER COLUMN b DROP DEFAULT")) + .isEqualTo(new DropDefaultValue( + location(1, 1), + QualifiedName.of(ImmutableList.of( + new Identifier(location(1, 23), "foo", false), + new Identifier(location(1, 27), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(new NodeLocation(1, 42), "b", false))), + true)); + } + @Test public void testAlterColumnSetDataType() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java index 055a1605be09..7ca41be0c6ae 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java @@ -590,6 +590,22 @@ default void addField(ConnectorSession session, ConnectorTableHandle tableHandle throw new TrinoException(NOT_SUPPORTED, "This connector does not support adding fields"); } + /** + * Set the specified default value + */ + default void setDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, String defaultValue) + { + throw new TrinoException(NOT_SUPPORTED, "This connector does not support setting default values"); + } + + /** + * Drop a default value on the specified column + */ + default void dropDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle) + { + throw new TrinoException(NOT_SUPPORTED, "This connector does not support dropping default values"); + } + /** * Set the specified column type */ diff --git a/docs/src/main/sphinx/sql/alter-table.md b/docs/src/main/sphinx/sql/alter-table.md index 9cd4d6a297c4..3bfdbd8adeb3 100644 --- a/docs/src/main/sphinx/sql/alter-table.md +++ b/docs/src/main/sphinx/sql/alter-table.md @@ -10,6 +10,8 @@ ALTER TABLE [ IF EXISTS ] name ADD COLUMN [ IF NOT EXISTS ] column_name data_typ [ FIRST | LAST | AFTER after_column_name ] ALTER TABLE [ IF EXISTS ] name DROP COLUMN [ IF EXISTS ] column_name ALTER TABLE [ IF EXISTS ] name RENAME COLUMN [ IF EXISTS ] old_name TO new_name +ALTER TABLE [ IF EXISTS ] name ALTER COLUMN column_name SET DEFAULT expression +ALTER TABLE [ IF EXISTS ] name ALTER COLUMN column_name DROP DEFAULT ALTER TABLE [ IF EXISTS ] name ALTER COLUMN column_name SET DATA TYPE new_type ALTER TABLE [ IF EXISTS ] name ALTER COLUMN column_name DROP NOT NULL ALTER TABLE name SET AUTHORIZATION ( user | USER user | ROLE role ) diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java index fc93e0d2c8cd..6b5bc3e2a678 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java @@ -390,6 +390,22 @@ public void addField(ConnectorSession session, ConnectorTableHandle tableHandle, } } + @Override + public void setDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, String defaultValue) + { + try (ThreadContextClassLoader _ = new ThreadContextClassLoader(classLoader)) { + delegate.setDefaultValue(session, tableHandle, column, defaultValue); + } + } + + @Override + public void dropDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle) + { + try (ThreadContextClassLoader _ = new ThreadContextClassLoader(classLoader)) { + delegate.dropDefaultValue(session, tableHandle, columnHandle); + } + } + @Override public void setColumnType(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, Type type) { diff --git a/plugin/trino-lakehouse/src/main/java/io/trino/plugin/lakehouse/LakehouseMetadata.java b/plugin/trino-lakehouse/src/main/java/io/trino/plugin/lakehouse/LakehouseMetadata.java index b70c7af086aa..7d81b9e6162e 100644 --- a/plugin/trino-lakehouse/src/main/java/io/trino/plugin/lakehouse/LakehouseMetadata.java +++ b/plugin/trino-lakehouse/src/main/java/io/trino/plugin/lakehouse/LakehouseMetadata.java @@ -432,6 +432,18 @@ public void addField(ConnectorSession session, ConnectorTableHandle tableHandle, forHandle(tableHandle).addField(session, tableHandle, parentPath, fieldName, type, ignoreExisting); } + @Override + public void setDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, String defaultValue) + { + forHandle(tableHandle).setDefaultValue(session, tableHandle, column, defaultValue); + } + + @Override + public void dropDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle) + { + forHandle(tableHandle).dropDefaultValue(session, tableHandle, columnHandle); + } + @Override public void setColumnType(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column, Type type) { diff --git a/plugin/trino-memory/src/main/java/io/trino/plugin/memory/MemoryMetadata.java b/plugin/trino-memory/src/main/java/io/trino/plugin/memory/MemoryMetadata.java index bdb93d8721df..34d371f2fc08 100644 --- a/plugin/trino-memory/src/main/java/io/trino/plugin/memory/MemoryMetadata.java +++ b/plugin/trino-memory/src/main/java/io/trino/plugin/memory/MemoryMetadata.java @@ -481,6 +481,36 @@ public synchronized void renameColumn(ConnectorSession session, ConnectorTableHa tables.put(tableId, new TableInfo(tableId, table.schemaName(), table.tableName(), ImmutableList.copyOf(columns), table.truncated(), table.dataFragments(), table.comment())); } + @Override + public synchronized void setDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle, String defaultValue) + { + MemoryTableHandle handle = (MemoryTableHandle) tableHandle; + MemoryColumnHandle column = (MemoryColumnHandle) columnHandle; + long tableId = handle.id(); + TableInfo table = tables.get(handle.id()); + + List columns = new ArrayList<>(table.columns()); + ColumnInfo columnInfo = columns.get(column.columnIndex()); + columns.set(column.columnIndex(), new ColumnInfo(columnInfo.handle(), Optional.of(defaultValue), columnInfo.nullable(), columnInfo.comment())); + + tables.put(tableId, new TableInfo(tableId, table.schemaName(), table.tableName(), ImmutableList.copyOf(columns), table.truncated(), table.dataFragments(), table.comment())); + } + + @Override + public synchronized void dropDefaultValue(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle) + { + MemoryTableHandle handle = (MemoryTableHandle) tableHandle; + MemoryColumnHandle column = (MemoryColumnHandle) columnHandle; + long tableId = handle.id(); + TableInfo table = tables.get(handle.id()); + + List columns = new ArrayList<>(table.columns()); + ColumnInfo columnInfo = columns.get(column.columnIndex()); + columns.set(column.columnIndex(), new ColumnInfo(columnInfo.handle(), Optional.empty(), columnInfo.nullable(), columnInfo.comment())); + + tables.put(tableId, new TableInfo(tableId, table.schemaName(), table.tableName(), ImmutableList.copyOf(columns), table.truncated(), table.dataFragments(), table.comment())); + } + @Override public synchronized void dropNotNullConstraint(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle columnHandle) { diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryDefaultColumnValue.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryDefaultColumnValue.java index 0fb999de4e8b..c89b9b1ac3d7 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryDefaultColumnValue.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryDefaultColumnValue.java @@ -216,5 +216,19 @@ private void assertDefaultValue(@Language("SQL") String columnType, @Language("S .skippingTypesCheck() .matches("VALUES " + expected); } + + try (TestTable table = newTrinoTable("test_default_value", "(id int, data " + columnType + ")")) { + assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN data SET DEFAULT " + literal); + assertUpdate("INSERT INTO " + table.getName() + " (id) VALUES (1)", 1); + assertThat(query("SELECT data FROM " + table.getName())) + .skippingTypesCheck() + .matches("VALUES " + expected); + + assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN data DROP DEFAULT"); + assertUpdate("INSERT INTO " + table.getName() + " (id) VALUES (2)", 1); + assertThat(query("SELECT data FROM " + table.getName())) + .skippingTypesCheck() + .matches("VALUES " + expected + ", NULL"); + } } } 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 5746daa7596d..67b822c8cad0 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 @@ -138,6 +138,7 @@ import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_DELETE; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_DEREFERENCE_PUSHDOWN; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_DROP_COLUMN; +import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_DROP_DEFAULT_COLUMN_VALUE; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_DROP_FIELD; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_DROP_FIELD_IN_ARRAY; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_DROP_NOT_NULL_CONSTRAINT; @@ -165,6 +166,7 @@ import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_ROW_LEVEL_UPDATE; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_ROW_TYPE; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_SET_COLUMN_TYPE; +import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_SET_DEFAULT_COLUMN_VALUE; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_SET_FIELD_TYPE; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_SET_FIELD_TYPE_IN_ARRAY; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_SET_FIELD_TYPE_IN_MAP; @@ -3146,6 +3148,48 @@ public void testRenameRowFieldCaseSensitivity() } } + @Test + public void testSetDefaultColumn() + { + skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_DEFAULT_COLUMN_VALUE)); + + try (TestTable table = newTrinoTable("test_set_default", "(col int)")) { + if (!hasBehavior(SUPPORTS_SET_DEFAULT_COLUMN_VALUE)) { + assertQueryFails("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DEFAULT NULL", ".* Catalog '.*' does not support default value for column .*"); + return; + } + + assertThat(getColumnDefault(table.getName(), "col")).isNull(); + + assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DEFAULT 123"); + assertThat(getColumnDefault(table.getName(), "col")).isEqualTo("123"); + } + } + + @Test + public void testDropDefaultColumn() + { + skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_DEFAULT_COLUMN_VALUE)); + + try (TestTable table = newTrinoTable("test_set_default", "(col int DEFAULT 123)")) { + if (!hasBehavior(SUPPORTS_DROP_DEFAULT_COLUMN_VALUE)) { + assertQueryFails("ALTER TABLE " + table.getName() + " ALTER COLUMN nationkey DROP DEFAULT", ".* Catalog '.*' does not support default value for column .*"); + return; + } + + assertThat(getColumnDefault(table.getName(), "col")).isEqualTo("123"); + + assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col DROP DEFAULT"); + assertThat(getColumnDefault(table.getName(), "col")).isNull(); + } + } + + protected String getColumnDefault(String tableName, String columnName) + { + return (String) computeScalar("SELECT column_default FROM information_schema.columns " + + "WHERE table_schema = CURRENT_SCHEMA AND table_name = '" + tableName + "' AND column_name = '" + columnName + "'"); + } + @Test public void testSetColumnType() { diff --git a/testing/trino-testing/src/main/java/io/trino/testing/TestingConnectorBehavior.java b/testing/trino-testing/src/main/java/io/trino/testing/TestingConnectorBehavior.java index a0e8b3ac8f6b..aae3e3c4922d 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/TestingConnectorBehavior.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/TestingConnectorBehavior.java @@ -115,6 +115,8 @@ public enum TestingConnectorBehavior SUPPORTS_COMMENT_ON_MATERIALIZED_VIEW_COLUMN(SUPPORTS_CREATE_MATERIALIZED_VIEW), SUPPORTS_DEFAULT_COLUMN_VALUE(SUPPORTS_CREATE_TABLE), + SUPPORTS_SET_DEFAULT_COLUMN_VALUE(SUPPORTS_DEFAULT_COLUMN_VALUE), + SUPPORTS_DROP_DEFAULT_COLUMN_VALUE(SUPPORTS_DEFAULT_COLUMN_VALUE), SUPPORTS_NOT_NULL_CONSTRAINT(SUPPORTS_CREATE_TABLE), SUPPORTS_ADD_COLUMN_NOT_NULL_CONSTRAINT(and(SUPPORTS_NOT_NULL_CONSTRAINT, SUPPORTS_ADD_COLUMN)), SUPPORTS_DROP_NOT_NULL_CONSTRAINT(SUPPORTS_NOT_NULL_CONSTRAINT),