[ 
https://issues.apache.org/jira/browse/FLINK-40924?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

sepuri sai krishna updated FLINK-40924:
---------------------------------------
    Release Note: 
table.exec.sink.require-on-conflict is now also enforced when 
table.exec.sink.upsert-materialize
is set to FORCE. A query whose upsert key differs from the sink's primary key 
and that specifies
no ON CONFLICT clause previously planned under FORCE and will now fail with the 
same
ValidationException already raised under AUTO. Add an ON CONFLICT clause, or set
require-on-conflict to false, to keep such a query planning. 
upsert-materialize=NONE is
unaffected, because it removes the sink upsert materializer and therefore 
applies no conflict
strategy.


  was:
table.exec.sink.require-on-conflict is now also enforced when
table.exec.sink.upsert-materialize is set to NONE or FORCE. Queries whose 
upsert key differs from
the sink's primary key and that specify no ON CONFLICT clause previously 
planned successfully
under those two settings and will now fail with the same ValidationException 
already raised under
AUTO. Set require-on-conflict to false to restore the previous behaviour.


     Description: 
{{table.exec.sink.require-on-conflict}} defaults to {{true}} and is documented 
to throw when the
query's upsert key differs from the sink's primary key and no {{ON CONFLICT}} 
clause is given.
Setting {{table.exec.sink.upsert-materialize}} to {{FORCE}} suppresses that 
error, and the query
plans with {{upsertMaterialize=[true]}}.

h3. Reproducer

The upsert key is the grouping key {{c}}, which is never written to the sink, 
so it cannot equal
the primary key {{x}}.

{code:sql}
CREATE TABLE src (a INT, b BIGINT, c STRING)
  WITH ('connector' = 'datagen', 'number-of-rows' = '5');

CREATE TABLE snk (x INT, y BIGINT, PRIMARY KEY (x) NOT ENFORCED)
  WITH ('connector' = 'blackhole');

SET 'table.exec.sink.upsert-materialize' = 'FORCE';

INSERT INTO snk SELECT MAX(a), COUNT(*) FROM src GROUP BY c;
{code}

With {{require-on-conflict}} left at its default of {{true}}:

|| upsert-materialize || result ||
| {{AUTO}} (default) | {{ValidationException}}: "The query has an upsert key 
that differs from the primary key of the sink table ... Please specify an ON 
CONFLICT clause" |
| {{FORCE}} | plans, {{upsertMaterialize=[true]}} |

The check is called from inside the {{AUTO}} branch of 
{{analyzeUpsertMaterializeStrategy}} in
{{FlinkChangelogModeInferenceProgram}}, so the other settings never reach it.

h3. Why FORCE should be covered

{{FORCE}} adds the sink upsert materializer, and the materializer is the only 
thing that applies a
conflict strategy. {{CommonExecSink}} gates it with
{{upsertMaterialize && (!inputInsertOnly || 
detectsDuplicateKeys(conflictStrategy))}}, {{DO ERROR}}
and {{DO NOTHING}} are implemented by {{WatermarkCompactingSinkMaterializer}} 
and
{{DO DEDUPLICATE}} by {{SinkUpsertMaterializer}}, both built only on that path. 
So the clause does
take effect under {{FORCE}}, and FLIP-558 states the requirement without tying 
it to a
materialization setting:

{quote}
If no ON CONFLICT DO ... is provided and the upsert key is different from the 
primary key of the
sink we would fail during the planning.
{quote}

Nothing documents {{FORCE}} as a way to opt out. The documented ways are
{{require-on-conflict=false}} and {{upsert-materialize=NONE}}.

h3. Why NONE is not part of this

{{NONE}} makes {{analyzeUpsertMaterializeStrategy}} return {{false}}, so no 
materializer is built
and an {{ON CONFLICT}} clause has no effect. Requiring one would ask for a 
clause that is then
ignored. It is also documented as the way to opt out entirely, in
{{docs/content/docs/sql/reference/dml/insert.md}} under "When is ON CONFLICT 
required?":

{quote}
Alternatively, if you do not need consistency guarantees for conflicting keys, 
you can disable the
sink upsert materializer entirely by setting table.exec.sink.upsert-materialize 
to NONE. This
removes the materializer operator from the pipeline, so no buffering, 
compaction, or conflict
resolution is performed. Records are passed directly to the sink as they arrive.
{quote}

So {{NONE}} stays as it is.

h3. Affects

{{master}} and {{release-2.3}}, where the relevant code is unchanged.

Related to FLINK-40899, which reports the opposite direction on the same 
validation: there it
fires too early and masks an NDU error. This one is the check not firing under 
{{FORCE}}.


  was:
{{table.exec.sink.require-on-conflict}} defaults to {{true}} and is documented 
to throw when the
query's upsert key differs from the sink's primary key and no {{ON CONFLICT}} 
clause is given.
Setting {{table.exec.sink.upsert-materialize}} to {{NONE}} suppresses that 
error, and the query
plans with no materializer.

h3. Reproducer

The upsert key is the grouping key {{c}}, which is never written to the sink, 
so it cannot equal
the primary key {{x}}.

{code:sql}
CREATE TABLE src (a INT, b BIGINT, c STRING)
  WITH ('connector' = 'datagen', 'number-of-rows' = '5');

CREATE TABLE snk (x INT, y BIGINT, PRIMARY KEY (x) NOT ENFORCED)
  WITH ('connector' = 'blackhole');

SET 'table.exec.sink.upsert-materialize' = 'NONE';

INSERT INTO snk SELECT MAX(a), COUNT(*) FROM src GROUP BY c;
{code}

With {{require-on-conflict}} left at its default of {{true}}:

|| upsert-materialize || result ||
| {{AUTO}} (default) | {{ValidationException}}: "The query has an upsert key 
that differs from the primary key of the sink table ... Please specify an ON 
CONFLICT clause" |
| {{FORCE}} | plans, {{upsertMaterialize=[true]}} |
| {{NONE}} | plans, {{upsertMaterialize=[false]}} |

Only {{AUTO}} raises the documented error.

h3. Why this looks unintended

The two options document separate concerns, and neither documents this 
interaction.

{{require-on-conflict}} documents exactly one way to turn itself off:

{quote}
Set this to false to restore the old behavior where no ON CONFLICT clause was 
required. Note that
disabling this check may lead to non-deterministic results in certain streaming 
scenarios.
{quote}

{{upsert-materialize}} is described entirely in terms of the materialize 
operator and shuffle
disorder, and says nothing about validation or {{ON CONFLICT}}.

So a user who sets {{NONE}} has opted out of materialization, not out of the 
check. The
{{FORCE}} case also skips the error but still materializes, so its result stays 
deterministic;
{{NONE}} skips the error and does not materialize.

h3. Affects

{{master}} and {{release-2.3}}, where the relevant code is unchanged.

Related to FLINK-40899, which reports the opposite direction on the same 
validation: there it
fires too early and masks an NDU error. This one is the check not firing at all.


         Summary: table.exec.sink.upsert-materialize=FORCE silently bypasses 
the require-on-conflict check  (was: table.exec.sink.upsert-materialize=NONE 
silently bypasses the require-on-conflict check)

> table.exec.sink.upsert-materialize=FORCE silently bypasses the 
> require-on-conflict check
> ----------------------------------------------------------------------------------------
>
>                 Key: FLINK-40924
>                 URL: https://issues.apache.org/jira/browse/FLINK-40924
>             Project: Flink
>          Issue Type: Bug
>          Components: Table SQL / Planner
>    Affects Versions: 2.3.0, 2.4.0
>            Reporter: sepuri sai krishna
>            Assignee: sepuri sai krishna
>            Priority: Major
>              Labels: pull-request-available
>
> {{table.exec.sink.require-on-conflict}} defaults to {{true}} and is 
> documented to throw when the
> query's upsert key differs from the sink's primary key and no {{ON CONFLICT}} 
> clause is given.
> Setting {{table.exec.sink.upsert-materialize}} to {{FORCE}} suppresses that 
> error, and the query
> plans with {{upsertMaterialize=[true]}}.
> h3. Reproducer
> The upsert key is the grouping key {{c}}, which is never written to the sink, 
> so it cannot equal
> the primary key {{x}}.
> {code:sql}
> CREATE TABLE src (a INT, b BIGINT, c STRING)
>   WITH ('connector' = 'datagen', 'number-of-rows' = '5');
> CREATE TABLE snk (x INT, y BIGINT, PRIMARY KEY (x) NOT ENFORCED)
>   WITH ('connector' = 'blackhole');
> SET 'table.exec.sink.upsert-materialize' = 'FORCE';
> INSERT INTO snk SELECT MAX(a), COUNT(*) FROM src GROUP BY c;
> {code}
> With {{require-on-conflict}} left at its default of {{true}}:
> || upsert-materialize || result ||
> | {{AUTO}} (default) | {{ValidationException}}: "The query has an upsert key 
> that differs from the primary key of the sink table ... Please specify an ON 
> CONFLICT clause" |
> | {{FORCE}} | plans, {{upsertMaterialize=[true]}} |
> The check is called from inside the {{AUTO}} branch of 
> {{analyzeUpsertMaterializeStrategy}} in
> {{FlinkChangelogModeInferenceProgram}}, so the other settings never reach it.
> h3. Why FORCE should be covered
> {{FORCE}} adds the sink upsert materializer, and the materializer is the only 
> thing that applies a
> conflict strategy. {{CommonExecSink}} gates it with
> {{upsertMaterialize && (!inputInsertOnly || 
> detectsDuplicateKeys(conflictStrategy))}}, {{DO ERROR}}
> and {{DO NOTHING}} are implemented by {{WatermarkCompactingSinkMaterializer}} 
> and
> {{DO DEDUPLICATE}} by {{SinkUpsertMaterializer}}, both built only on that 
> path. So the clause does
> take effect under {{FORCE}}, and FLIP-558 states the requirement without 
> tying it to a
> materialization setting:
> {quote}
> If no ON CONFLICT DO ... is provided and the upsert key is different from the 
> primary key of the
> sink we would fail during the planning.
> {quote}
> Nothing documents {{FORCE}} as a way to opt out. The documented ways are
> {{require-on-conflict=false}} and {{upsert-materialize=NONE}}.
> h3. Why NONE is not part of this
> {{NONE}} makes {{analyzeUpsertMaterializeStrategy}} return {{false}}, so no 
> materializer is built
> and an {{ON CONFLICT}} clause has no effect. Requiring one would ask for a 
> clause that is then
> ignored. It is also documented as the way to opt out entirely, in
> {{docs/content/docs/sql/reference/dml/insert.md}} under "When is ON CONFLICT 
> required?":
> {quote}
> Alternatively, if you do not need consistency guarantees for conflicting 
> keys, you can disable the
> sink upsert materializer entirely by setting 
> table.exec.sink.upsert-materialize to NONE. This
> removes the materializer operator from the pipeline, so no buffering, 
> compaction, or conflict
> resolution is performed. Records are passed directly to the sink as they 
> arrive.
> {quote}
> So {{NONE}} stays as it is.
> h3. Affects
> {{master}} and {{release-2.3}}, where the relevant code is unchanged.
> Related to FLINK-40899, which reports the opposite direction on the same 
> validation: there it
> fires too early and masks an NDU error. This one is the check not firing 
> under {{FORCE}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to