Fabian Hueske created FLINK-40477:
-------------------------------------

             Summary: Partial deletes fail sink NOT NULL enforcement
                 Key: FLINK-40477
                 URL: https://issues.apache.org/jira/browse/FLINK-40477
             Project: Flink
          Issue Type: Bug
          Components: Table SQL / Runtime
    Affects Versions: 2.3.0
            Reporter: Fabian Hueske
            Assignee: Fabian Hueske


In a partial-delete (key-only) pipeline, a -D tombstone carries the key and 
sets all non-key columns to null, "regardless of nullability constraints" (see 
ChangelogFunction / ChangelogMode#keyOnlyDeletes).

When the sink has a NOT NULL value (non-key) column, the sink constraint 
enforcer (NotNullConstraint) inspects the tombstone and reacts to that null:
 * table.exec.sink.not-null-enforcer = ERROR (default): throws 
EnforcerException → the job fails on every by-key delete (crash-restart loop).
 * table.exec.sink.not-null-enforcer = DROP: the tombstone is silently dropped 
→ the row is never deleted (stale data, no error).


Root cause: NotNullConstraint#enforce (module flink-table-runtime, package 
...operators.sink.constraint) checks input.isNullAt(index) for every row 
regardless of RowKind; it has no notion that a DELETE in a key-only-delete 
pipeline legitimately carries null non-key columns.

This is distinct from FLINK-40468 (composite serializer NPE): that requires a 
null nested inside a composite constructor, whereas this affects any NOT NULL 
top-level value column and is caught earlier, at the enforcer.

How to reproduce (scalar INT column, so no composite/serializer path is 
involved):

 
{code:java}
-- source produces key-only deletes; sink accepts key-only deletes
CREATE TABLE src (id INT PRIMARY KEY NOT ENFORCED, v INT NOT NULL) WITH (
  'connector' = 'values', 
  'changelog-mode' = 'I,UA,D',
  'source.produces-delete-by-key' = 'true', ...);                             

CREATE TABLE snk (id INT PRIMARY KEY NOT ENFORCED, v INT NOT NULL) WITH (
  'connector' = 'values', 'sink-insert-only' = 'false',
  'sink.supports-delete-by-key' = 'true');

INSERT INTO snk SELECT id, v FROM src;
-- Source data: +I(1, 10), +I(2, 20), -D(1, null) (tombstone: key only, v 
null).               {code}
 
 * Expected: the tombstone is applied by key; final sink state is \{(2, 20)}.
 * Actual (ERROR): EnforcerException: Column 'v' is NOT NULL, however, a null 
value is being written into it.
 * Actual (DROP): tombstone dropped; final sink state is \{(1, 10), (2, 20)} 
(stale row 1).

Suggested fix: make the enforcer skip the NOT NULL check for the non-key 
columns of a DELETE row when the input is a key-only-delete pipeline 
(ChangelogMode#keyOnlyDeletes). Key columns and non-DELETE rows stay fully 
enforced; full-delete pipelines are unaffected.

 



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

Reply via email to