Skip to content

fix: = ANY(array) on an indexed column returns no rows#477

Open
earfman wants to merge 2 commits into
oguimbal:masterfrom
earfman:patch-1
Open

fix: = ANY(array) on an indexed column returns no rows#477
earfman wants to merge 2 commits into
oguimbal:masterfrom
earfman:patch-1

Conversation

@earfman

@earfman earfman commented Jul 13, 2026

Copy link
Copy Markdown

What

col = ANY(array) silently returned zero rows when col is indexed (primary key or explicit index). This fixes it and adds a regression test. Fixes #338.

This also resolves the mystery in that thread: the reporter's repro used a SERIAL PRIMARY KEY (indexed → broken path), while the unit test they added — and the name = any(...) case that worked — used non-indexed columns (seq-scan path, correct). The divergence was the index all along.

Why

buildComparison (src/transforms/build-filter.ts) optimizes col = <constant> into a scalar index equality. When the right operand is ANY(array), the constant is the whole array, so the generated EqFilter looks up an index key equal to ['A','C'] and matches nothing. Without an index, the seq-scan fallback evaluates ANY element-wise and returns the correct rows — hence the index-only divergence.

This is a common query shape (WHERE id = ANY($1) against a primary key is exactly what Sequelize, Mikro-ORM, etc. generate), and the failure is silent — an empty result set, no error.

The fix

Skip the scalar index optimization in buildComparison when either operand is an ANY/ALL expression, so it falls back to the seq-scan path that already evaluates array membership correctly:

// `col = ANY(array)` / `col > ANY(array)` (and ALL) must NOT be optimized into a
// scalar index lookup: the ANY/ALL operand is an array, so comparing an indexed
// column against the array *value* matches no rows (silently returning nothing).
// Fall back to a seq scan, which evaluates ANY/ALL element-wise. See buildBinaryAny.
if (leftValue.isAny || rightValue.isAny) {
    return null;
}

Tests

  • Added src/tests/any-indexed-column.spec.ts — two cases (ARRAY[] and a string array literal) against a primary key column. Both fail on master and pass with this change.
  • Full suite: 846 → 847 pass, with identical skip/fail/error counts (the 3 pre-existing failures are the ORM integration tests that require a live Postgres, unrelated to this change).
  • The fix additionally repairs > ANY and != ANY on indexed columns, which returned empty/incorrect rows before.

Scope

Minimal — an 8-line guard in build-filter.ts plus a standalone regression test. No behavior change for non-indexed columns, IN (...), = ANY(subquery), or scalar comparisons.


This change was prepared with AI assistance and independently verified against the project's full test suite before submission (baseline-vs-fixed diff, and the regression test confirmed to fail without the fix).

earfman added 2 commits July 13, 2026 00:34
buildComparison optimized `col = <const>` into a scalar index equality; with an ANY(array) operand the constant is the whole array, so the index lookup matched no rows. Skip that optimization when an operand is ANY/ALL and fall back to the seq scan, which evaluates membership element-wise.
Fails on master, passes with the buildComparison fix on this branch. Exercises the primary-key (index) path for `= ANY(array)` via both ARRAY[] and a string array literal.
@earfman

earfman commented Jul 13, 2026

Copy link
Copy Markdown
Author

Coretexa gate report — pgmem-any-idx

fix: = ANY(array) on an indexed column returns no rows (pg-mem)

status: closed · doer: doer-claude · verifier: verifier-pgmem · repo: oguimbal/pg-mem@master

Acceptance criteria

  • col = ANY(array) on an indexed column (primary key / explicit index) matches the same rows as on a non-indexed column
  • Existing behavior unchanged for =, IN (...), = ANY(subquery), and non-indexed columns
  • pg-mem's full test suite passes with no new failures; a regression test is added that fails without the fix

Evidence

check result by detail
repro ✓ pass verifier-pgmem indexed = ANY(array['A','C']) and = ANY('{A,C}')[A,C]; PK, explicit index, and no-index all correct
regression-guard ✓ pass verifier-pgmem reverted the guard → new test FAILS (Received: []); restored → passes. Genuinely protective.
full-suite ✓ pass verifier-pgmem baseline 846 pass / 3 fail / 3 err → fixed 847 pass / 3 fail / 3 err (+1 pass, identical failures)
no-regression ✓ pass verifier-pgmem =, IN, = ANY(subquery), !=, scalar comparisons all still correct; 3 pre-existing fails are ORM tests needing a live Postgres
over-delivery ✓ pass verifier-pgmem fix also repairs > ANY and != ANY on indexed columns (returned empty/garbage before)

All required checks have passing evidence: repro, regression-guard, full-suite, no-regression

Verification

--- VERIFIED 2026-07-12 by verifier-pgmem (independent session, not the doer) ---
Gate 1: reproduced with own scripts — indexed = ANY(array/literal/subquery) all correct; non-broken paths intact.
Gate 2: ran full suite baseline (git stash) vs fixed — exactly +1 pass, same 3 fails / 3 errors (pre-existing ORM integration tests).
Gate 3: diff is an 8-line guard at the top of buildComparison, before the isConstant short-circuit and all index EqFilter/IneqFilter paths; comment accurate; isAny is a real IValue property.
Gate 4: reverted only the guard → the new regression test fails; also confirmed > ANY and != ANY on indexed columns are repaired; ALL is a pre-existing unsupported feature (identical both sides), not a gap.

Verified by a second agent · claimer ≠ verifier · coretexa.dev

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.

= ANY does not return any rows

1 participant