Skip to content

Commit

Permalink
Table feature interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
vkorukanti committed Feb 5, 2025
1 parent a6db4e0 commit dff2c0f
Show file tree
Hide file tree
Showing 14 changed files with 450 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,55 @@ public class TableConfig<T> {
// TableConfigs //
//////////////////

/**
* Whether this Delta table is append-only. Files can't be deleted, or values can't be updated.
*/
public static final TableConfig<Boolean> APPEND_ONLY =
new TableConfig<>(
"delta.appendOnly",
"false",
Boolean::valueOf,
value -> true,
"needs to be a boolean.",
true);

/**
* Enable change data feed output. When enabled, DELETE, UPDATE, and MERGE INTO operations will
* need to do additional work to output their change data in an efficiently readable format.
*/
public static final TableConfig<Boolean> CHANGE_DATA_FEED =
new TableConfig<>(
"delta.enableChangeDataFeed",
"false",
Boolean::valueOf,
value -> true,
"needs to be a boolean.",
true);

/** Whether commands modifying this Delta table are allowed to create new deletion vectors. */
public static final TableConfig<Boolean> ENABLE_DELETION_VECTORS_CREATION =
new TableConfig<>(
"delta.enableDeletionVectors",
"false",
Boolean::valueOf,
value -> true,
"needs to be a boolean.",
true);

/**
* Indicates whether Row Tracking is enabled on the table. When this flag is turned on, all rows
* are guaranteed to have Row IDs and Row Commit Versions assigned to them, and writers are
* expected to preserve them by materializing them to hidden columns in the data files.
*/
public static final TableConfig<Boolean> ROW_TRACKING_ENABLED =
new TableConfig<>(
"delta.enableRowTracking",
"false",
Boolean::valueOf,
value -> true,
"needs to be a boolean.",
true);

/**
* The shortest duration we have to keep logically deleted data files around before deleting them
* physically.
Expand Down Expand Up @@ -170,6 +219,19 @@ public class TableConfig<T> {
"needs to be a boolean.",
true);

/**
* Whether widening the type of an existing column or field is allowed, either manually using
* ALTER TABLE CHANGE COLUMN or automatically if automatic schema evolution is enabled.
*/
public static final TableConfig<Boolean> ENABLE_TYPE_WIDENING =
new TableConfig<>(
"delta.enableTypeWidening",
"false",
Boolean::valueOf,
value -> true,
"needs to be a boolean.",
true);

/** All the valid properties that can be set on the table. */
private static final Map<String, TableConfig<?>> VALID_PROPERTIES =
Collections.unmodifiableMap(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.delta.kernel.internal.metrics.SnapshotQueryContext;
import io.delta.kernel.internal.metrics.SnapshotReportImpl;
import io.delta.kernel.internal.snapshot.SnapshotManager;
import io.delta.kernel.internal.tablefeatures.TableFeatures;
import io.delta.kernel.internal.util.Clock;
import io.delta.kernel.metrics.SnapshotReport;
import io.delta.kernel.types.StructField;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.delta.kernel.internal.replay.LogReplay;
import io.delta.kernel.internal.snapshot.LogSegment;
import io.delta.kernel.internal.snapshot.SnapshotHint;
import io.delta.kernel.internal.tablefeatures.TableFeatures;
import io.delta.kernel.internal.util.ColumnMapping;
import io.delta.kernel.internal.util.ColumnMapping.ColumnMappingMode;
import io.delta.kernel.internal.util.SchemaUtils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import io.delta.kernel.internal.replay.ConflictChecker;
import io.delta.kernel.internal.replay.ConflictChecker.TransactionRebaseState;
import io.delta.kernel.internal.rowtracking.RowTracking;
import io.delta.kernel.internal.tablefeatures.TableFeatures;
import io.delta.kernel.internal.util.*;
import io.delta.kernel.metrics.TransactionReport;
import io.delta.kernel.types.StructType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import static io.delta.kernel.internal.util.VectorUtils.stringArrayValue;

import io.delta.kernel.data.*;
import io.delta.kernel.internal.TableFeatures;
import io.delta.kernel.internal.data.GenericRow;
import io.delta.kernel.internal.tablefeatures.TableFeatures;
import io.delta.kernel.internal.util.Tuple2;
import io.delta.kernel.internal.util.VectorUtils;
import io.delta.kernel.types.ArrayType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import io.delta.kernel.internal.actions.SetTransaction;
import io.delta.kernel.internal.rowtracking.RowTracking;
import io.delta.kernel.internal.rowtracking.RowTrackingMetadataDomain;
import io.delta.kernel.internal.tablefeatures.TableFeatures;
import io.delta.kernel.internal.util.DomainMetadataUtils;
import io.delta.kernel.internal.util.FileNames;
import io.delta.kernel.utils.CloseableIterable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import io.delta.kernel.data.FilteredColumnarBatch;
import io.delta.kernel.engine.Engine;
import io.delta.kernel.expressions.Predicate;
import io.delta.kernel.internal.TableFeatures;
import io.delta.kernel.internal.actions.*;
import io.delta.kernel.internal.checkpoints.SidecarFile;
import io.delta.kernel.internal.fs.Path;
Expand All @@ -35,6 +34,7 @@
import io.delta.kernel.internal.metrics.SnapshotMetrics;
import io.delta.kernel.internal.snapshot.LogSegment;
import io.delta.kernel.internal.snapshot.SnapshotHint;
import io.delta.kernel.internal.tablefeatures.TableFeatures;
import io.delta.kernel.internal.util.DomainMetadataUtils;
import io.delta.kernel.internal.util.Tuple2;
import io.delta.kernel.types.StringType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
import io.delta.kernel.data.Row;
import io.delta.kernel.internal.DeltaErrors;
import io.delta.kernel.internal.SnapshotImpl;
import io.delta.kernel.internal.TableFeatures;
import io.delta.kernel.internal.actions.*;
import io.delta.kernel.internal.tablefeatures.TableFeatures;
import io.delta.kernel.utils.CloseableIterable;
import io.delta.kernel.utils.CloseableIterator;
import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import static io.delta.kernel.internal.DeltaErrors.wrapEngineExceptionThrowsIO;
import static io.delta.kernel.internal.TableConfig.EXPIRED_LOG_CLEANUP_ENABLED;
import static io.delta.kernel.internal.TableConfig.LOG_RETENTION;
import static io.delta.kernel.internal.TableFeatures.validateWriteSupportedTable;
import static io.delta.kernel.internal.replay.LogReplayUtils.assertLogFilesBelongToTable;
import static io.delta.kernel.internal.snapshot.MetadataCleanup.cleanupExpiredLogs;
import static io.delta.kernel.internal.tablefeatures.TableFeatures.validateWriteSupportedTable;
import static io.delta.kernel.internal.util.Preconditions.checkArgument;
import static java.lang.String.format;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.delta.kernel.internal.tablefeatures;

import io.delta.kernel.internal.actions.Metadata;
import io.delta.kernel.internal.actions.Protocol;

/**
* Extension of {@link TableFeature} that can be automatically enabled via a change in a table's
* metadata, e.g., through setting particular values of certain feature-specific table properties.
*
* <p>When the feature's metadata requirements are satisfied for <strong>new tables</strong>, or for
* <strong>existing tables when {@link #automaticallyUpdateProtocolOfExistingTables()} set to
* `true`</strong>, the client will silently add the feature to the protocol's `readerFeatures`
* and/or `writerFeatures`. Otherwise, a proper protocol version bump must be present in the same
* transaction.
*/
public interface FeatureAutoEnablementByMetadata {
/**
* Whether the feature can automatically update the protocol of an existing table when the
* metadata requirements are satisfied. As a rule of thumb, a table feature that requires explicit
* operations (e.g., turning on a table property) should set this flag to `true`, while features
* that are used implicitly (e.g., when using a new data type) should set this flag to `false`.
*/
default boolean automaticallyUpdateProtocolOfExistingTables() {
return true;
}

/**
* Determine whether the feature must be supported and enabled because its metadata requirements
* are satisfied.
*/
boolean metadataRequiresFeatureToBeEnabled(Protocol protocol, Metadata metadata);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package io.delta.kernel.internal.tablefeatures;

import static io.delta.kernel.internal.util.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
* Base class for table features.
*
* <p>A feature can be <b>explicitly supported</b> by a table's protocol when the protocol contains
* a feature's `name`. Writers (for writer-only features) or readers and writers (for reader-writer
* features) must recognize supported features and must handle them appropriately.
*
* <p>A table feature that released before Delta Table Features (reader version 3 and writer version
* 7) is considered as a <strong>legacy feature</strong>. Legacy features are <strong> implicitly
* supported</strong> when (a) the protocol does not support table features, i.e., has reader
* version less than 3 or writer version less than 7 and (b) the feature's minimum reader/writer
* version is less than or equal to the current protocol's reader/writer version.
*
* <p>Separately, a feature can be automatically supported by a table's metadata when certain
* feature-specific table properties are set. For example, `changeDataFeed` is automatically
* supported when there's a table property `delta.enableChangeDataFeed=true`. This is independent of
* the table's enabled features. When a feature is supported (explicitly or implicitly) by the table
* protocol but its metadata requirements are not satisfied, then clients still have to understand
* the feature (at least to the extent that they can read and preserve the existing data in the
* table that uses the feature).
*/
public class TableFeature {
private final String featureName;
private final int minReaderVersion;
private final int minWriterVersion;
private final boolean readerWriterFeature;
private final boolean isLegacyFeature;
private final List<TableFeature> requiredFeatures;
private final Optional<FeatureAutoEnablementByMetadata> featureAutoEnablementByMetadata;

/**
* Create a new table feature.
*
* @param featureName a globally-unique string indicator to represent the feature. All characters
* must be letters (a-z, A-Z), digits (0-9), '-', or '_'. Words must be in camelCase.
* @param minReaderVersion the minimum reader version this feature requires. For a feature that
* can only be explicitly supported, this is either `0` or `3` (the reader protocol version
* that supports table features), depending on the feature is writer-only or reader-writer.
* For a legacy feature that can be implicitly supported, this is the first protocol version
* which the feature is introduced.
* @param minWriterVersion the minimum writer version this feature requires. For a feature that
* can only be explicitly supported, this is the writer protocol `7` that supports table
* features. For a legacy feature that can be implicitly supported, this is the first protocol
* version which the feature is introduced.
* @param isLegacyTableFeature this feature is a legacy feature? i.e a feature that released
* before Delta Table Features (reader version 3 and writer version 7).
* @param requiredFeatures Set of table features that this table feature depends on. I.e. the set
* of features that need to be enabled if this table feature is enabled.
*/
public TableFeature(
String featureName,
int minReaderVersion,
int minWriterVersion,
boolean readerWriterFeature,
boolean isLegacyTableFeature,
List<TableFeature> requiredFeatures,
Optional<FeatureAutoEnablementByMetadata> featureAutoEnablementByMetadata) {
this.featureName = requireNonNull(featureName, "name is null");
checkArgument(!featureName.isEmpty(), "name is empty");
checkArgument(
featureName.chars().allMatch(c -> Character.isLetterOrDigit(c) || c == '-' || c == '_'),
"name contains invalid characters: " + featureName);
this.minReaderVersion = minReaderVersion;
this.minWriterVersion = minWriterVersion;
if (!readerWriterFeature) {
checkArgument(minReaderVersion == 0, "Writer-only feature must have minReaderVersion=0");
}
this.readerWriterFeature = readerWriterFeature;
this.isLegacyFeature = isLegacyTableFeature;
this.requiredFeatures =
Collections.unmodifiableList(requireNonNull(requiredFeatures, "requiredFeatures is null"));
this.featureAutoEnablementByMetadata =
requireNonNull(featureAutoEnablementByMetadata, "featureAutoEnablementByMetadata is null");

featureAutoEnablementByMetadata.ifPresent(
autoEnablementByMetadata -> {
checkArgument(
isLegacyFeature()
&& autoEnablementByMetadata.automaticallyUpdateProtocolOfExistingTables(),
"Legacy feature must be auto-update capable.");
});
}
/** @return the name of the table feature. */
String featureName() {
return featureName;
}

/**
* @return true if this feature is applicable to both reader and writer, false if it is
* writer-only.
*/
boolean isReaderWriterFeature() {
return readerWriterFeature;
}

/** @return the minimum reader version this feature requires */
int minReaderVersion() {
return minReaderVersion;
}

/** @return the minimum writer version that this feature requires. */
int minWriterVersion() {
return minWriterVersion;
}

/** @return if this feature is a legacy feature? */
boolean isLegacyFeature() {
return isLegacyFeature;
}

/**
* Set of table features that this table feature depends on. I.e. the set of features that need to
* be enabled if this table feature is enabled.
*
* @return the list of table features that this table feature depends on.
*/
List<TableFeature> requiredFeatures() {
return requiredFeatures;
}
}
Loading

0 comments on commit dff2c0f

Please sign in to comment.