Skip to content

rewrite phrase SQL by remove JOIN with group by (doc_id, anchor) - #24761

Merged
mergify[bot] merged 3 commits into
matrixorigin:mainfrom
cpegeric:fulltext_pharse_rewrite
Jun 2, 2026
Merged

rewrite phrase SQL by remove JOIN with group by (doc_id, anchor)#24761
mergify[bot] merged 3 commits into
matrixorigin:mainfrom
cpegeric:fulltext_pharse_rewrite

Conversation

@cpegeric

@cpegeric cpegeric commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

  • API-change
  • BUG
  • Improvement
  • Documentation
  • Feature
  • Test and CI
  • Code Refactoring

Which issue(s) this PR fixes:

issue #24760 #24684

What this PR does / why we need it:

In fulltext search, a boolean-mode query with many optional (OR) words
— e.g. 25 words becomes very slow. Tracing the path explains why:

 - With the ngram parser, a bare word is not a single word = '...' lookup. GenTextSql
 (pkg/fulltext/sql.go:242-260) re-tokenizes each word into overlapping 3-grams and routes it
 through SqlPhrase, which reconstructs the word with a positional self-join of its grams
 (a ⌈L/3⌉-way join for an L-char word).
 - In all-optional boolean mode, SqlBoolean emits one such phrase search per OR term,
 UNION ALL'd. So 25 OR words = 25 positional gram-reconstruction joins. The "many JOINs"
 are the per-OR phrase searches, not joins between words.

 The multi-pattern branch of SqlPhrase (pkg/fulltext/sql.go:569-613) builds an N-way join:

 WITH kw0 AS (SELECT doc_id,pos FROM idx WHERE word='g0'),
      kw1 AS (...), ... kwN AS (...)
 SELECT kw0.doc_id [,CAST(0 as int)] FROM kw0,...,kwN
 WHERE kw0.doc_id=kw1.doc_id AND kw1.pos-kw0.pos=off1 AND ...   -- residual positional filter
 [GROUP BY kw0.doc_id]   -- boolean only

 The planner can only treat doc_id as a clean equi-key; the positional part is a residual
 filter applied after a per-doc cross-product of each gram's positions. Cost is multiplicative
 in per-gram occurrences and grows with both gram count and word count.

 The same N-way-join generator (SqlPhrase) backs all phrase paths: NL/default phrase,
 boolean quoted phrase, and (via GenTextSql) every ngram OR term. So one rewrite fixes all of
 them. The index table is CLUSTER BY (word) (pkg/sql/plan/build_ddl.go:~1994), so the
 per-gram word= / prefix_eq lookups are already efficient sorted-range scans — the join is
 the problem, not the lookups.

 Intended outcome: make each phrase reconstruction linear, removing the per-OR join blow-up,
 with identical result semantics so the downstream consumer is untouched.

 The fix: replace the N-way positional join with anchor grouping

 A phrase occurrence is exactly a set of positions where every gram i sits at
 pos = anchor + offset_i for one common anchor (offset_i = the gram's distance from the
 first gram). Normalize each gram hit to its anchor (anchor = pos − offset_i), UNION ALL the
 per-gram hits, then group by (doc_id, anchor): a group containing all N grams is one phrase
 occurrence. Linear in the sum of per-gram posting lists; no cross-product; no N-relation
 join-order decision.

 This is provably equivalent to the current predicate: all slots share an anchor iff
 pos_i − pos_0 = offset_i for every i, which is the existing kwi.pos − kw0.pos = off_i.
 COUNT(*) = N is correct even with repeated words/grams (N = slot count): the group key
 includes anchor (not just doc_id), so multiple occurrences of the same word in one sentence
 fall into different anchor buckets, and each slot contributes at most one row per anchor (a
 word can't sit at two positions both equal to anchor + off_i). Worked example confirming this:
 query "a b a" over sentence "a b a b a" yields exactly two occurrences (anchors 0 and 2),
 matching the old join. (A COUNT(DISTINCT slot)=N variant with a per-slot tag is available if a
 provably-duplicate-immune form is ever wanted, but is not needed here.)

 Rewrite only the len(ps) > 1 branch of SqlPhrase (pkg/fulltext/sql.go:569-613). Build,
 for each pattern slot i (base = ps[0].Position, offset_i = ps[i].Position − base):

 SELECT doc_id, pos - <offset_i> AS anchor FROM <idxtbl> WHERE <cond_i>
 where cond_i is word = '<esc>' (TEXT) or prefix_eq(word,'<esc>') (STAR) — reusing the
 existing escape + *-suffix handling.

 Let innerUnion = <slot selects joined by " UNION ALL "> and proj = withIndex ? "doc_id, CAST(0 as int)" : "doc_id".

 - NL / default mode (one row per occurrence → preserves tf):
 SELECT <proj> FROM (<innerUnion>) anchors GROUP BY doc_id, anchor HAVING COUNT(*) = <N>
 - Boolean mode (collapse to one row per doc, matching today's GROUP BY kw0.doc_id):
 SELECT <proj> FROM (
   SELECT doc_id FROM (<innerUnion>) anchors GROUP BY doc_id, anchor HAVING COUNT(*) = <N>
 ) phrase GROUP BY doc_id

 The single-pattern branch (len(ps) == 1) and all boolean set-algebra
 (GenJoinSql/GenJoinPlusSql/GenSql OR/MINUS assembly) are unchanged — in particular we
 do not apply HAVING COUNT=N across OR terms, which would wrongly require all words.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@mergify

mergify Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-06-02 08:17 UTC · Rule: main
  • Checks started · in-place
  • Checks failed
  • 🚫 Left the queue2026-06-02 10:12 UTC · at c4babda4297f48d6b21247791f3d8c082c7d7118

This pull request spent 1 hour 55 minutes 27 seconds in the queue, with no time running CI.

Waiting for
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
All conditions
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-decision = APPROVED [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / SCA Test on Ubuntu/x86
    • check-neutral = Matrixone CI / SCA Test on Ubuntu/x86
    • check-skipped = Matrixone CI / SCA Test on Ubuntu/x86

Reason

The merge conditions cannot be satisfied due to failing checks

Hint

You may have to fix your CI before adding the pull request to the queue again.
If you update this pull request, to fix the CI, it will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a @mergifyio queue comment.

@mergify

mergify Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-06-02 11:29 UTC · Rule: main
  • Checks skipped · PR is already up-to-date
  • Merged2026-06-02 11:29 UTC · at c4babda4297f48d6b21247791f3d8c082c7d7118 · squash

This pull request spent 34 seconds in the queue, including 7 seconds running CI.

Required conditions to merge
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-decision = APPROVED [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / SCA Test on Ubuntu/x86
    • check-neutral = Matrixone CI / SCA Test on Ubuntu/x86
    • check-skipped = Matrixone CI / SCA Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(Optimistic/PUSH)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH,Optimistic)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-neutral = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
    • check-skipped = Matrixone Upgrade CI / Compatibility Test With Target on Linux/x64(LAUNCH)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Utils CI / Coverage
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage

@mergify
mergify Bot merged commit fd6d9f1 into matrixorigin:main Jun 2, 2026
42 of 44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/bug Something isn't working size/M Denotes a PR that changes [100,499] lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants