-
Notifications
You must be signed in to change notification settings - Fork 14k
[FLINK-40557][table] Make operation column optional in TO_CHANGELOG…
#29102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -286,7 +286,7 @@ Table result = cdcStream | |||||
|
|
||||||
| ## TO_CHANGELOG | ||||||
|
|
||||||
| The `TO_CHANGELOG` PTF converts a dynamic table (i.e. an updating table) into an append-only table with an explicit operation code column. Each input row - regardless of its original change operation (INSERT, UPDATE_BEFORE, UPDATE_AFTER, DELETE) - is emitted as an INSERT-only row with a string column indicating the original operation. | ||||||
| The `TO_CHANGELOG` PTF converts a dynamic table (i.e. an updating table) into an append-only table. By default, each input row - regardless of its original change operation (INSERT, UPDATE_BEFORE, UPDATE_AFTER, DELETE) - is emitted as an INSERT-only row with a string column indicating the original operation. Set `include_op_column` to `false` to omit that column. | ||||||
|
|
||||||
| This is useful when you need to materialize changelog events into a downstream system that only supports appends (e.g., a message queue, log store, or append-only file sink). It is also useful to filter out certain types of updates, for example DELETEs. | ||||||
|
|
||||||
|
|
@@ -297,7 +297,8 @@ SELECT * FROM TO_CHANGELOG( | |||||
| input => TABLE source_table [PARTITION BY key_col], | ||||||
| [op => DESCRIPTOR(op_column_name),] | ||||||
| [op_mapping => MAP['INSERT', 'I', 'DELETE', 'D', ...],] | ||||||
| [produces_full_deletes => BOOLEAN] | ||||||
| [produces_full_deletes => BOOLEAN,] | ||||||
| [include_op_column => BOOLEAN] | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having an optimal param with false as default feels more intuitive. If the user wants a different behavior he can set it to true.
Suggested change
We didn't consider that with produces_full_deletes but it was the way the FLIP was approved so we didn't change. Open to alternative naming suggestions. |
||||||
| ) | ||||||
| ``` | ||||||
|
|
||||||
|
|
@@ -309,6 +310,7 @@ SELECT * FROM TO_CHANGELOG( | |||||
| | `op` | No | A `DESCRIPTOR` with a single column name for the operation code column. Defaults to `op`. | | ||||||
| | `op_mapping` | No | A `MAP<STRING, STRING>` mapping change operation names to custom output codes. Keys can contain comma-separated names to map multiple operations to the same code (e.g., `'INSERT, UPDATE_AFTER'`). When provided, only mapped operations are forwarded - unmapped events are dropped. Each change operation may appear at most once across all entries. | | ||||||
| | `produces_full_deletes` | No | A `BOOLEAN` literal that controls how DELETE rows are emitted. When `true` (default), DELETE rows carry all columns, the full image. When `false`, only the identifying key columns are preserved and the rest are nulled. See [Full vs partial deletes](#full-vs-partial-deletes) for more details. | | ||||||
| | `include_op_column` | No | A `BOOLEAN` literal that controls whether the operation code column is included in the output. When `true` (default), the operation code column is prepended to the output. When `false`, the output schema contains only the input columns. | | ||||||
|
|
||||||
| #### Default op_mapping | ||||||
|
|
||||||
|
|
@@ -323,12 +325,14 @@ When `op_mapping` is omitted, all four change operations are mapped to their sta | |||||
|
|
||||||
| ### Output Schema | ||||||
|
|
||||||
| The output schema is: | ||||||
| By default, the output schema is: | ||||||
|
|
||||||
| ``` | ||||||
| [op_column, all_input_columns] | ||||||
| ``` | ||||||
|
|
||||||
| With `include_op_column => false`, the output schema contains only the input columns. | ||||||
|
|
||||||
| All output rows have `INSERT` - the table is always append-only. | ||||||
|
|
||||||
| ### Examples | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,7 @@ public final class ToChangelogTypeStrategy { | |
| public static final int ARG_OP = 1; | ||
| public static final int ARG_OP_MAPPING = 2; | ||
| public static final int ARG_PRODUCES_FULL_DELETES = 3; | ||
| public static final int ARG_INCLUDE_OP_COLUMN = 4; | ||
|
|
||
| private static final Set<String> VALID_ROW_KIND_NAMES = | ||
| Set.of("INSERT", "UPDATE_BEFORE", "UPDATE_AFTER", "DELETE"); | ||
|
|
@@ -83,15 +84,16 @@ public Optional<List<DataType>> inferInputTypes( | |
| new ValidationException( | ||
| "First argument must be a table for TO_CHANGELOG.")); | ||
|
|
||
| final String opColumnName = | ||
| ChangelogTypeStrategyUtils.resolveOpColumnName(callContext); | ||
| final boolean producesFullDeletes = | ||
| callContext | ||
| .getArgumentValue(ARG_PRODUCES_FULL_DELETES, Boolean.class) | ||
| .orElse(true); | ||
|
|
||
| final boolean includeOpColumn = shouldIncludeOpColumn(callContext); | ||
|
|
||
| final List<Field> outputFields = | ||
| buildOutputFields(semantics, opColumnName, producesFullDeletes); | ||
| buildOutputFields( | ||
| semantics, producesFullDeletes, includeOpColumn, callContext); | ||
|
|
||
| return Optional.of(DataTypes.ROW(outputFields).notNull()); | ||
| }; | ||
|
|
@@ -100,6 +102,17 @@ public Optional<List<DataType>> inferInputTypes( | |
| // Helpers | ||
| // -------------------------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * Returns whether {@code TO_CHANGELOG} should include the operation column in its output. | ||
| * | ||
| * <p>Compiled plans created before this argument was introduced have only four arguments and | ||
| * retain the default value of {@code true}. | ||
| */ | ||
| public static boolean shouldIncludeOpColumn(final CallContext callContext) { | ||
| return callContext.getArgumentDataTypes().size() <= ARG_INCLUDE_OP_COLUMN | ||
| || callContext.getArgumentValue(ARG_INCLUDE_OP_COLUMN, Boolean.class).orElse(true); | ||
| } | ||
|
|
||
| private static Optional<List<DataType>> validateInputs( | ||
| final CallContext callContext, final boolean throwOnFailure) { | ||
| Optional<List<DataType>> error; | ||
|
|
@@ -227,12 +240,16 @@ private static boolean mapsDelete(final Map<String, String> opMapping) { | |
| */ | ||
| private static List<Field> buildOutputFields( | ||
| final TableSemantics semantics, | ||
| final String opColumnName, | ||
| final boolean producesFullDeletes) { | ||
| final boolean producesFullDeletes, | ||
| final boolean includeOpColumn, | ||
| final CallContext callContext) { | ||
| final List<Field> inputFields = DataType.getFields(semantics.dataType()); | ||
| final int[] outputIndices = ChangelogTypeStrategyUtils.computeOutputIndices(semantics); | ||
| final List<Field> outputFields = new ArrayList<>(); | ||
| outputFields.add(DataTypes.FIELD(opColumnName, DataTypes.STRING())); | ||
| if (includeOpColumn) { | ||
| final String opColumnName = ChangelogTypeStrategyUtils.resolveOpColumnName(callContext); | ||
| outputFields.add(DataTypes.FIELD(opColumnName, DataTypes.STRING())); | ||
| } | ||
|
Comment on lines
+249
to
+252
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If |
||
| final Set<Integer> preserved = | ||
| producesFullDeletes | ||
| ? Collections.emptySet() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,7 @@ public List<TableTestProgram> programs() { | |
| ToChangelogTestPrograms.RETRACT_PARTITION_BY, | ||
| ToChangelogTestPrograms.CUSTOM_OP_MAPPING, | ||
| ToChangelogTestPrograms.CUSTOM_OP_NAME, | ||
| ToChangelogTestPrograms.WITHOUT_OP_COLUMN, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please add a |
||
| ToChangelogTestPrograms.TABLE_API_DEFAULT, | ||
| ToChangelogTestPrograms.TABLE_API_RETRACT_PARTITION_BY, | ||
| ToChangelogTestPrograms.LAG_ON_UPSERT_VIA_CHANGELOG, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessary as part of the short explanation of what the function does. The user can see it's an option below.