8000 fix: the DDL waiter parsed IF NOT EXISTS as the column name by minguyen9988 · Pull Request #1408 · Altinity/clickhouse-sink-connector · GitHub
[go: up one dir, main page]

Skip to content

fix: the DDL waiter parsed IF NOT EXISTS as the column name - #1408

Merged
minguyen9988 merged 1 commit into
2.10.0from
omniwatcher/ddl-waiter-if-not-exists
Aug 25, 2026
Merged

minguyen9988 merged 1 commit into
2.10.0from
omniwatcher/ddl-waiter-if-not-exists

Conversation

@minguyen9988
@minguyen9988 minguyen9988 commented Aug 25, 2026 •
Copy link
Copy Markdown
Collaborator

Fixes the java-lightweight failures in run 32834092832 and the JUnit Test Report job that reports them.

The schema-change waiter extracted the wrong column name from every guarded ALTER, so it waited for a column that can never exist and never waited for the real one — reinstating the exact race (#1222) it was written to close.

ALTER TABLE employees.ship_class
  ADD COLUMN IF NOT EXISTS ship_spec Nullable(String) first,
  ADD COLUMN IF NOT EXISTS somecol Nullable(Int32) after start_build

Waiting for 2 column(s) to appear in employees.ship_class: [IF, IF]

ADD_COLUMN_PATTERN captured the first token after ADD COLUMN. That was correct until the generated DDL became guarded; the pattern predates the guards and was never updated, so it now returns the keyword IF. DROP_COLUMN_PATTERN has the same defect with IF EXISTS.

Consequences, all observed in CI

  • the waiter polls system.columns for a column named IF for the full 30 s timeout — ~600 polls per table — then gives up;
  • the real column is never waited for, so the caller reads column metadata that may predate the ALTER — the stale-column-list race this class exists to prevent;
  • the test then dereferences a column map that was never populated and dies with a NullPointerException far from the cause.

Fix

Consume the optional guard in the prefix instead of capturing it. A column legitimately named `if` still extracts correctly, since the guard only matches when IF introduces the EXISTS clause.

The timeout warning now names the misparse too. A wrong column name degraded silently for the whole timeout and then surfaced as an unrelated NPE; the warning says so where the operator will see it.

Why this passed CI before

Every test in the class re-declared the regex inline rather than using the shipped constant, so the copies stayed correct while the real pattern broke. The patterns are now package-private and the new tests assert against them directly.

Tests — 25 in the class, 4 new

The verbatim failing-CI DDL, the guarded drop form, the unguarded form still working, and a column actually named `if`.

Failing-first verified — reverting the two patterns fails exactly the new tests:

addColumnIfNotExistsIsNotTheColumnName:147
  expected: <[ship_spec, somecol]> but was: <[IF, IF]>
dropColumnIfExistsIsNotTheColumnName:159
  expected: <[extra]> but was: <[IF]>

Integration tests

Run locally against the suites that were failing at aa00431a, all now green:

AlterTableAddColumnIT        1/1
AlterTableAddColumnRaceIT    1/1
AlterTableDropColumnCacheIT  1/1
MySQLDemoIT                  6 run, 0 failures, 0 errors   (was 2 failures + 3 errors)

Base branch 2.10.0.

The schema-change waiter extracted the wrong column name from every guarded
ALTER, so it waited for a column that can never exist and never waited for the
real one -- reinstating the exact race (#1222) it was written to close.

    ALTER TABLE employees.ship_class
      ADD COLUMN IF NOT EXISTS ship_spec Nullable(String) first,
      ADD COLUMN IF NOT EXISTS somecol Nullable(Int32) after start_build

    Waiting for 2 column(s) to appear in employees.ship_class: [IF, IF]

ADD_COLUMN_PATTERN captured the first token after ADD COLUMN. That was correct
until the generated DDL became guarded; the pattern predates the guards and was
never updated, so it now returns the keyword IF. DROP_COLUMN_PATTERN has the
same defect with IF EXISTS.

Consequences, all observed in CI:
  * the waiter polls system.columns for a column named IF for the full 30s
    timeout, ~600 polls per table, then gives up;
  * the real column is never waited for, so the caller reads column metadata
    that may predate the ALTER -- the stale-column-list race the class exists
    to prevent;
  * the test then dereferences a column map that was never populated and dies
    with a NullPointerException far from the cause.

Fix: consume the optional guard in the prefix instead of capturing it. A
column legitimately named 'if' still extracts correctly, since the guard only
matches when IF introduces the EXISTS clause.

Also names the misparse in the timeout warning. A wrong column name degraded
silently for the whole timeout and then surfaced as an unrelated NPE; the
warning now says so where the operator will see it.

WHY THIS PASSED CI BEFORE: every test in the class re-declared the regex
inline rather than using the shipped constant, so the copies stayed correct
while the real pattern broke. The patterns are now package-private and the new
tests assert against them directly.

Tests: DDLSchemaChangeWaiterRaceConditionTest 25 (4 new) -- the verbatim
failing-CI DDL, the guarded drop form, the unguarded form still working, and a
column actually named 'if'.

Failing-first verified. Reverting the two patterns fails exactly the new tests:
  addColumnIfNotExistsIsNotTheColumnName:147
    expected: <[ship_spec, somecol]> but was: <[IF, IF]>
  dropColumnIfExistsIsNotTheColumnName:159
    expected: <[extra]> but was: <[IF]>

Verified against the integration tests that were failing at aa00431
(run 32834092832, job java-lightweight), all now green:
  AlterTableAddColumnIT        1/1
  AlterTableAddColumnRaceIT    1/1
  AlterTableDropColumnCacheIT  1/1
  MySQLDemoIT                  6 run, 0 failures, 0 errors (was 2 failures + 3 errors)

Destructive-Op-Check: sites=2; result=pass
@minguyen9988 minguyen9988 changed the title WIP: DO NOT MERGE - fix: the DDL waiter parsed IF NOT EXISTS as the column name fix: the DDL waiter parsed IF NOT EXISTS as the column name Aug 25, 2026
@minguyen9988
minguyen9988 merged commit cbeec7f into 2.10.0 Aug 25, 2026
7 of 9 checks passed
@minguyen9988
minguyen9988 deleted the omniwatcher/ddl-waiter-if-not-exists branch August 25, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0