Skip to content

routing: fix rule validation, MQ dispatch and view rewriting - #6259

Open
3AceShowHand wants to merge 20 commits into
pingcap:masterfrom
3AceShowHand:table-route-enhancement
Open

3AceShowHand wants to merge 20 commits into
pingcap:masterfrom
3AceShowHand:table-route-enhancement

Conversation

@3AceShowHand

@3AceShowHand 3AceShowHand commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

What problem does this PR solve?

Issue Number: close #6264

Also fixes stale table identities after EXCHANGE PARTITION, which can cause subsequent DML to use the previous table's route.

What is changed and how it works?

  • Honor changefeed-level case sensitivity and validate route matchers even when no source tables exist.
  • Skip table-only routing rules during MQ dispatch matching, preserving topic and partition selection by subsequent MQ rules.
  • Substitute route placeholders in one pass so placeholder-like text in source names remains literal.
  • Rewrite CREATE VIEW column and wildcard qualifiers with their physical tables, preserving aliases and CTE references across nested and recursive scopes.
  • Build EXCHANGE PARTITION events with the current logical table identity and update participating dispatchers' cached table information so subsequent DML follows the current table's route.
  • Emit the schema verification success log only after verification succeeds.

Check List

Tests

Questions

Will it cause performance regression or break compatibility?

No configuration or event-format changes. Routing behavior changes for the affected cases described above. No performance benchmarks were run.

Do you need to update user documentation, design documentation or monitoring documentation?

The dispatch-rule comments now clarify how table-only routing and matcher-only rules affect MQ dispatch. No new configuration or monitoring settings are introduced.

Release note

Fix table routing validation, case sensitivity, literal placeholder handling, and CREATE VIEW rewriting. Preserve MQ dispatch behavior for table-only routing rules and correctly route DML after EXCHANGE PARTITION.

Summary by CodeRabbit

  • New Features

    • Improved table routing for case-sensitive and case-insensitive identifiers.
    • Enhanced routing of views, correlated subqueries, CTEs, and database-level DDL.
    • Added support for continued DML routing after partition exchanges.
    • Added validation for empty routing targets and invalid route patterns.
    • Added table-state metadata for more reliable DDL event processing.
  • Bug Fixes

    • Preserved changefeed bootstrap state during status updates and retries.
    • Missing changefeed deletion requests now require authentication.
    • Corrected handling of literal placeholder-like table names.
    • Improved dispatcher metadata handling during partition exchanges.

Replace route placeholders in one pass so placeholder-like text in source
schema and table names remains literal. Add substitution and DDL routing
regression coverage.
Validate route matchers for empty table sets and keep table-only routing
rules out of MQ dispatch matching. Report successful table verification
accurately and improve routing/configuration regression coverage.
@ti-chi-bot ti-chi-bot Bot added do-not-merge/needs-linked-issue release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Sep 14, 2026
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 19997b4a-5f8b-44c5-ab24-7115a7ac4875

📥 Commits

Reviewing files that changed from the base of the PR and between 02455b6 and 07ebedb.

📒 Files selected for processing (3)
  • downstreamadapter/eventcollector/dispatcher_stat_test.go
  • logservice/schemastore/persist_storage.go
  • logservice/schemastore/persist_storage_ddl_handlers.go

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change updates route validation, case-sensitive matching, scope-aware DDL rewriting, exchange-partition metadata handling, bootstrap status preservation, and related API, unit, and integration tests.

Changes

Routing validation and case sensitivity

Layer / File(s) Summary
Dispatch validation and route identity
api/v2/*, downstreamadapter/sink/eventrouter/*, pkg/config/*, pkg/routing/{admin,registry,router}*
Validation now runs when dispatch rules exist without tables. Table-only rules are skipped by the event router. Case sensitivity is passed explicitly and applied to route identities, registry conflicts, admissions, and dispatch matching.
Bootstrap status handling
coordinator/changefeed/*
BootstrapDone remains set when an incoming status omits it. Bootstrap transition handling records the new checkpoint, with regression coverage.

DDL rewriting and exchange handling

Layer / File(s) Summary
Scope-aware DDL rewriting
pkg/routing/ddl_query_rewriter.go, pkg/common/event/ddl_query_normalizer.go, pkg/routing/*_test.go
DDL routing resolves physical tables, aliases, CTEs, wildcards, correlated references, and database names through nested scopes. Placeholder substitution preserves literal source names.
Exchange-partition metadata
pkg/common/event/ddl_event.go, logservice/schemastore/*, downstreamadapter/eventcollector/*
DDL events carry table-state changes. Schema-store events and dispatcher cache updates use physical table identity and extracted table information for exchange-partition handling.

Verification and integration

Layer / File(s) Summary
Verification and table-routing scenarios
logservice/schemastore/validator.go, tests/integration_tests/table_route/*
Successful eligibility checks emit the completion log. Integration tests verify correlated views, CTE shadowing, literal names, and data consistency after partition exchange.

Priority: ➖ Normal

Estimated code review effort: 5 (Critical) | ~90 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant SourceDDL
  participant SchemaStore
  participant DDLRewriter
  participant Router
  participant Dispatcher
  SourceDDL->>SchemaStore: emit DDL event with table state
  SchemaStore->>DDLRewriter: provide table metadata
  DDLRewriter->>Router: resolve tables, aliases, CTEs, and scopes
  Router->>DDLRewriter: return routed identifiers
  DDLRewriter->>Dispatcher: deliver routed DDL
  Dispatcher->>Dispatcher: update cached table information
Loading
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the issue, implementation changes, compatibility impact, documentation impact, and release note. However, the required Tests checklist is empty. Add at least one applicable test option under the Tests section, such as Unit test or Integration test, and include test commands or results where relevant.
Out of Scope Changes check ⚠️ Warning The pull request contains changes without a demonstrated connection to issue #6264. The coordinator bootstrap status changes, authenticated changefeed deletion and keyspace-loading tests, EXCHANGE PAR… Remove the unrelated coordinator, API, EXCHANGE PARTITION, and schema verification changes from this pull request, or move them to separate pull requests linked to the appropriate issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 34.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 81 functions across 29 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary changes to route validation, MQ dispatch, and view rewriting.
Linked Issues check ✅ Passed The pull request satisfies the coding requirements in issue #6264. It passes changefeed-level case sensitivity to routing and dispatch, validates matchers without source tables, prevents table-only ru…
Full details: Out of Scope Changes check

Explanation

The pull request contains changes without a demonstrated connection to issue #6264. The coordinator bootstrap status changes, authenticated changefeed deletion and keyspace-loading tests, EXCHANGE PARTITION event and table-state changes, related integration tests, and schema verification logging do not implement the linked issue's route matching, MQ dispatch, placeholder substitution, or CREATE VIEW requirements.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit hops through routes so wide
With scoped DDL close by its side
CTEs keep their names in place
Partitions carry state with grace
Bootstrap flags remain true
And tests check every path anew

Comment @coderabbitai help to get the list of available commands.

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/test all

@3AceShowHand 3AceShowHand changed the title Table route enhancement routing: fix rule validation, MQ dispatch and view rewriting Sep 15, 2026
@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/test all

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/test all

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/test all

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/retest

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/test all

1 similar comment
@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/test all

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/retest

Table-qualified references were only bound to tables of the current SELECT,
so a correlated reference kept the source table name after routing and the
rewritten statement pointed at a table that no longer exists. References are
now resolved through the SELECT scope chain after the whole statement is
visited: an alias, a CTE name, or an ambiguous declaration stops the search,
and columns and wildcards share the same rules. The CREATE VIEW normalizer
implements the same rules for the source schema; both sides cross-reference
the rule set and pin it with mirrored test cases.

Physical table identity now follows the changefeed's case-sensitive setting
everywhere two names are compared: rule matching, statement rewriting, route
conflict detection, and route admission. A case-insensitive changefeed treats
`T` and `t` as one table, so two sources that map to case-different targets
now conflict instead of silently writing into one downstream table.

Tests cover the resolution matrix (shadowing, ambiguity, correlated, union,
derived table, cross schema), case-sensitive binding, registry/admin identity,
and two new table_route integration views (aliased and nested correlated).
Rewrite one DDL statement in a single AST pass: route and rename each table
name where it is visited instead of extracting names first and matching them
positionally. This removes the name extractor, the default-schema fill, the
position indexes, and the target-count checks. A statement that routes nothing
keeps its original text, as before. One corner changes: a schema-qualified
reference is routed by rule and no longer requires the referenced table to
appear in the same statement.

Registry and admin cleanups: inline the single-caller remove(), share the
conflict report, and read the case-sensitivity flag from the registry instead
of duplicating it on Admin. Test and script cleanups follow: TestResolveDDL
drives the public entry point with rules derived from its expected mappings,
and the table_route script shares one correlated-view helper.
@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/test all

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/retest

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/retest

@ti-chi-bot ti-chi-bot Bot added the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Sep 18, 2026
if current != nil {
expectedTableID = current.(*common.TableInfo).TableName.TableID
}
if ddl.TableInfo.TableName.TableID != expectedTableID {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the immediate EXCHANGE PARTITION issue, but it leaks DDL-specific semantics into Event Collector. Event Collector should not need to know which DDL types can change the logical owner of a physical table, and BlockedTables should not be used to infer a schema mutation: it represents barrier participation, not whether the dispatcher's cached TableInfo must be replaced.

Could we model the per-dispatcher state transition explicitly in DDLEvent instead? For example:

type TableStateChangeKind uint8

  const (
      TableStateUnchanged TableStateChangeKind = iota
      TableStateUpdated
      TableStateDeleted
  )

  type TableStateChange struct {
      // The physical table ID whose dispatcher should apply this change.
      PhysicalTableID int64

      Kind  TableStateChangeKind
      After *common.TableInfo
  }

  type DDLEvent struct {
      // Existing event-level metadata used by DDL routing, rewriting, and sinks.
      TableInfo          *common.TableInfo
      MultipleTableInfos []*common.TableInfo

      // The state transition for the table dispatcher receiving this event.
      // It should be nil for the table-trigger event.
      TableStateChange *TableStateChange
  }

  Schema Store already has most of the required abstraction in extractTableInfoFunc:

  extractTableInfoFunc(
      event *PersistedDDLEvent,
      physicalTableID int64,
  ) (tableInfo *common.TableInfo, deleted bool)

Its return values already represent the three required states:

tableInfo != nil -> Updated
tableInfo == nil && !deleted -> Unchanged
tableInfo == nil && deleted -> Deleted

Instead of implementing the physical-to-logical table mapping again in individual buildDDLEventFunc implementations, fetchTableDDLEvents could attach the state change at a common boundary:

  handler := allDDLHandlers[model.ActionType(rawEvent.Type)]

  ddlEvent, ok, err := handler.buildDDLEventFunc(
      &rawEvent,
      tableFilter,
      tableID,
  )
  if err != nil || !ok {
      // existing handling
  }

  tableInfo, deleted :=
      handler.extractTableInfoFunc(&rawEvent, tableID)

  switch {
  case tableInfo != nil:
      ddlEvent.TableStateChange = &commonEvent.TableStateChange{
          PhysicalTableID: tableID,
          Kind:            commonEvent.TableStateUpdated,
          After:           tableInfo,
      }

  case deleted:
      ddlEvent.TableStateChange = &commonEvent.TableStateChange{
          PhysicalTableID: tableID,
          Kind:            commonEvent.TableStateDeleted,
      }

  default:
      ddlEvent.TableStateChange = &commonEvent.TableStateChange{
          PhysicalTableID: tableID,
          Kind:            commonEvent.TableStateUnchanged,
      }
  }

This makes extractTableInfoFunc the single source of truth for both:

  1. updating versionedTableInfoStore; and
  2. describing the post-DDL state delivered to a table dispatcher.

The table-trigger event should continue carrying the event-level TableInfo and MultipleTableInfos required by routing and sinks, but it should not carry a per-table TableStateChange.

The router also needs to apply table routing to TableStateChange.After, just as it currently routes TableInfo and MultipleTableInfos, because Event Collector must cache the routed table identity:

  if ddl.TableStateChange != nil &&
      ddl.TableStateChange.After != nil {
      ddl.TableStateChange.After, err =
          router.ApplyToTableInfo(ddl.TableStateChange.After)
  }

With this contract, Event Collector becomes DDL-agnostic:

  d.tableInfoVersion.Store(ddl.FinishedTs)

  change := ddl.TableStateChange
  if change == nil {
      return
  }

  if change.PhysicalTableID != tableSpan.TableID {
      log.Error(
          "table state change was delivered to the wrong dispatcher",
          zap.Int64("expectedPhysicalTableID", tableSpan.TableID),
          zap.Int64("actualPhysicalTableID", change.PhysicalTableID),
      )
      return
  }

  switch change.Kind {
  case commonEvent.TableStateUnchanged:
      return

  case commonEvent.TableStateUpdated:
      if change.After == nil {
          log.Error("updated table state does not contain TableInfo")
          return
      }
      d.tableInfo.Store(change.After)

  case commonEvent.TableStateDeleted:
      // Dispatcher removal is handled by the scheduling/barrier workflow.
      return
  }

Please also include TableStateChange in:

  • DDLEvent marshal/unmarshal;
  • routed-event cloning;
  • size accounting, if applicable;
  • mixed-version compatibility handling.

The tests should verify the contract at the Schema Store boundary:

ALTER TABLE -> Updated
CREATE VIEW broadcast -> Unchanged
CREATE TABLE LIKE referenced table -> Unchanged
EXCHANGE: old normal physical ID -> Updated to partition table
EXCHANGE: old partition ID -> Updated to normal table
DROP TABLE -> Deleted

@ti-chi-bot

ti-chi-bot Bot commented Sep 18, 2026

Copy link
Copy Markdown

@coderabbitai[bot]: adding LGTM is restricted to approvers and reviewers in OWNERS files.

Details

In response to this:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/test all

- Drop TableStateDeleted: the event collector no-ops every non-Updated kind.
- Remove the redundant nil TableInfo guard; the schema store only marks Updated
  together with the extracted table info.
- Trim assertions duplicated by the DDL event round-trip and rolling upgrade tests.
@ti-chi-bot ti-chi-bot Bot added lgtm and removed needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels Sep 20, 2026
@ti-chi-bot

ti-chi-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: coderabbitai[bot], lidezhu, wk989898
Once this PR has been reviewed and has the lgtm label, please assign nongfushanquan for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot

ti-chi-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-09-18 04:41:32.256977847 +0000 UTC m=+352938.194635441: ☑️ agreed by wk989898.
  • 2026-09-20 01:02:41.000964233 +0000 UTC m=+512606.938621826: ☑️ agreed by lidezhu.

@3AceShowHand

Copy link
Copy Markdown
Collaborator Author

/test all

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

routing: incorrect rule matching and CREATE VIEW rewriting

3 participants