Skip to content

fix(lucene): render slice-typed fields as array containment (#224) - #228

Open
Lutherwaves wants to merge 23 commits into
mainfrom
fix/lucene-array-field-containment
Open

fix(lucene): render slice-typed fields as array containment (#224)#228
Lutherwaves wants to merge 23 commits into
mainfrom
fix/lucene-array-field-containment

Conversation

@Lutherwaves

@Lutherwaves Lutherwaves commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Closes #224.

The bug

A slice-typed model field — Tags []string mapped to a Postgres text[] column — rendered as scalar equality:

tags:golang  ->  "tags" = ?

Postgres rejects that with malformed array literal. Critically ParseToSQL returned err = nil, so a caller that validated the filter saw it as valid and only discovered the problem as a database error — a 500 where a 400 belonged.

Semantics

Containment, matching Lucene/Elasticsearch: a multi-valued field is queried exactly like a single-valued one, so tags:golang matches a row whose tags contain golang.

Containment tags:golang Wildcard tags:*go* Has-value tags:*
postgresql COALESCE("tags" @> ARRAY[?], false) EXISTS (SELECT 1 FROM unnest("tags") …ILIKE ?) "tags" IS NOT NULL
mysql COALESCE(JSON_CONTAINS(`tags`, JSON_QUOTE(?)), false) JSON_SEARCH(`tags`, 'one', ?) IS NOT NULL \`tags\` IS NOT NULL
sqlite EXISTS (SELECT 1 FROM json_each("tags") WHERE value = ?) EXISTS (… json_each … LIKE ?) "tags" IS NOT NULL
dynamodb contains(tags, ?) rejected — PartiQL contains() on a list is membership, not substring

>, <, >=, <= and ranges now return a parse error naming the field, instead of emitting SQL that fails at the database.

Why @> and not = ANY

Postgres' own docs lead with 10000 = ANY(pay_by_quarter), but = ANY cannot use a GIN index. Measured on a 200k-row table:

tags @> ARRAY['t42']   ->  Bitmap Index Scan on t_gin_idx
't42' = ANY(tags)      ->  Seq Scan

docs/lucene.md now tells users to create that index, since GORM will not.

Second bug fixed along the way

The existing wildcard path cast the whole array to text and substring-matched it, so a pattern could span the separator between two elements. Verified: {alpha,beta} matched %ha,be%. Per-element matching fixes that.

Adjacent fixes (in the way, so fixed here)

  • MySQL identifier quoting was broken package-wide. quoteColumnName double-quoted identifiers for every provider, but MySQL's default sql_mode lacks ANSI_QUOTES, so "tags" was the string literal 'tags' — every MySQL query this package generated compared against a constant instead of a column. Existing MySQL tests only asserted a ? appeared, which is why it survived. Now uses backticks.
  • convertWildcards corrupted exact-match parameters. It ran on every value, so tags:"what?" bound what_. Harmless under LIKE, silently wrong under containment. The equality path now bypasses it.
  • The NOT keyword bypassed array handling entirely (found by the final whole-branch review). expr.Not was never dispatched, so NOT tags:golang fell through to the base driver and reproduced the original bug. Only the -tags:golang spelling worked. Both spellings now work, on all providers.
  • Grouped values (tags:(golang OR rust)) took a separate path with = hardcoded, reproducing the bug through documented syntax. Also fixed the scalar twin, where title:(hello* OR world) bound hello% under =.
  • docs/lucene.md claimed in two places that a Go slice is a JSONB column reachable via dot notation. parser.go explicitly rejects that. Both corrected.

Deliberate decisions worth reviewing

  • tags:* renders IS NOT NULL, so it still matches rows holding an empty array. Elasticsearch's exists would not. Chosen to preserve the existing row set (verified: 3 of 4 fixture rows before and after; the per-element form returns 2). Documented as a divergence.
  • Fuzzy is NOT failed closed. tags:foo~2 renders similarity(tags::text, ?) today and executes, so rejecting it would remove working behaviour — unlike ranges and ordering operators, which are already broken.
  • Non-string element arrays are cast to the column's exact element type (::bigint[], ::smallint[], ::double precision[], …). Postgres @> requires exactly matching array types and rejects both narrowing and widening.

Testing

Every pre-existing test in this package compares rendered SQL strings and never executes them — structurally why this bug survived 45 test functions, since "tags" = ? was exactly the string the code intended to emit.

This PR adds sql_executed_test.go, which drives real filters through NewParserParseToSQL and executes the result against in-memory SQLite, asserting on returned rows. No new dependency (mattn/go-sqlite3 was already in the module graph). Stashing the source fix makes those tests fail, so the harness genuinely catches the regression.

go test ./..., go vet ./... and gofmt are clean.

Known limitations (documented)

  • MySQL array wildcards are case-sensitive. JSON_SEARCH compares under utf8mb4_bin, so tags:*Go* will not match ["golang"] there, unlike Postgres ILIKE and SQLite LIKE. No MySQL instance was available to verify a fix, so the behaviour is documented rather than changed blind.
  • MySQL rendering is string-asserted only for the same reason.
  • gorm:"serializer:json" slices are stored as JSON but detected as native arrays, so Postgres receives @> ARRAY[?] against a JSON column and errors. Use an explicitly JSON-named type for JSON-backed arrays.
  • A []byte field is a scalar blob, not an array. Slice types whose name contains JSON/JSONB keep the nested-access path.

Follow-ups filed

Summary by CodeRabbit

  • New Features

    • Added support for searching array and slice fields with equality, containment, grouped values, wildcards, null checks, and logical expressions.
    • Improved array query handling across PostgreSQL, MySQL, SQLite, and DynamoDB.
    • Added provider-specific SQL formatting and PostgreSQL element-type casting.
  • Bug Fixes

    • Prevented wildcard patterns from matching across array elements.
    • Added clear errors for unsupported array comparisons, ranges, and wildcard operations.
    • Preserved correct handling of typed values, nulls, and negated conditions.
  • Documentation

    • Expanded guidance on array queries, indexing, provider behavior, casting, limitations, and supported operators.

The extracted walker's fallback treats its return as final, so the
Must/MustNot non-expression-operand recovery chain (serializeColumn,
then serializeValue, then Base.RenderParam) that existed pre-refactor
needs to live in the driver's fallback closure, not in the dialect-
free walker. Also restores NOT-wrapping around a successful chain
result for MustNot, which the walker's fallback path does not apply
itself.

Adds a regression test covering both a raw column operand and a raw
scalar operand on Must/MustNot to guard this refactor-fidelity path.
MySQL's default sql_mode lacks ANSI_QUOTES, so double-quoted identifiers were
parsed as string literals — every generated MySQL query compared against the
constant column name instead of the column.
…ality

Closes the core of #224: tags:golang now renders provider-appropriate
containment instead of "tags" = ?, which Postgres rejected with
'malformed array literal'.
…ent type

Postgres @> requires exactly matching array types; neither narrowing nor
widening rescues a mismatch. Map Go int/int64 to bigint[], float64 to
double precision[] and float32 to real[], replacing the blanket int[] and
numeric[] casts that broke []int64 and []float64 outright.
The @> operator requires exactly matching array types, so a smallint[]
column rejects ARRAY[?]::int[]. Give each Go element width the Postgres
array type that round-trips it exactly, and correct the comment: GORM
never creates array columns, so it cannot justify the mapping.
…alue

Casting an array to text let patterns span element boundaries, so
{alpha,beta} matched '%ha,be%'. Bare field:* stays IS NOT NULL so rows
holding an empty array keep matching.
… test

JSON_SEARCH compares under utf8mb4_bin regardless of collation, so the
MySQL array-wildcard branch is case-sensitive unlike Postgres ILIKE,
SQLite LIKE, and this same provider's own LOWER(...)-wrapped scalar
branch. No live MySQL is reachable here to verify a fix, so the SQL is
left as-is with the limitation documented in a comment.

Also assert the MySQL array-wildcard case no longer contains the old
scalar LOWER(...) construct, closing a gap where notSQL was empty.
Turns a runtime database error into a parse error naming the field. Fuzzy is
deliberately excluded: it currently executes via ::text similarity, so failing
closed there would remove working behaviour.
…renderer

tags:(a OR b) took a separate code path with '=' hardcoded, reproducing #224
through documented syntax.
Every prior test compared rendered SQL strings and never ran them, which is
structurally why #224 survived 45 test functions.
Also corrects two claims that a Go slice is a JSONB column reachable via dot
notation - parser.go explicitly rejects nested access on slices.
`-tags:golang` and `NOT tags:golang` are different go-lucene operators
(MustNot vs Not). Only MustNot was dispatched into the drivers' own
walker; Not fell through to `driver.Base.RenderParam`, which re-emitted
`"tags" = ?` and `"tags" SIMILAR TO ?` — issue #224, still reproducible
through that spelling on both the SQL and DynamoDB drivers.

Verified against a live Postgres before the fix:

    NOT tags:golang -> NOT("tags" = ?)          ERROR: malformed array literal
    NOT tags:*go*   -> NOT("tags" SIMILAR TO ?) ERROR: operator does not exist

Also fixed in this pass, all on array fields:

- DynamoDB `tags:(golang OR rust)` handed the whole OR sub-tree to
  `extractLiteralValue`, binding the stringified expression
  ("id:golang OR id:rust") as one value. It now expands to one
  `contains()` per member, mirroring the SQL driver.
- DynamoDB accepted `tags:>5` and `tags:[a TO b]`, rendering comparisons
  that silently match nothing. They now fail closed with an error naming
  the field, as the SQL driver already did.
- A wildcard leaf inside a group (`tags:(golang* OR rust)`) fell through
  to containment and bound the raw pattern. It now takes the same
  per-element path as the ungrouped `tags:golang*`. The scalar case had
  the same shape (binding `hello%` under `=`) and now renders ILIKE.

Negation over `field:null` is deliberately kept out of the walker: it
still collapses to `IS NOT NULL` for both spellings and both array and
scalar fields, matching the base driver rather than emitting
`NOT (field IS NULL)`.

`TestExecuted_Issue224Repro` re-asserted row counts already covered by
`TestExecuted_ArrayContainment`; it now asserts what the issue actually
reported — that a nil error from `ParseToSQL` implies executable SQL.
…aviour

The array section already claimed `NOT tags:golang` worked; that is now true.
Adds the `-tags:golang` and `NOT tags:null` equivalents, the grouped
wildcard form, and a DynamoDB paragraph covering contains() expansion and
the wildcard/ordering/range rejections.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Lutherwaves, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ea6de35-6857-4c2d-ac35-bf30d7d0b789

📥 Commits

Reviewing files that changed from the base of the PR and between 7d5fcf2 and 3b1f01c.

📒 Files selected for processing (6)
  • storage/search/lucene/arrayfield.go
  • storage/search/lucene/arrayfield_test.go
  • storage/search/lucene/dynamodb_driver.go
  • storage/search/lucene/sql_driver.go
  • storage/search/lucene/sql_driver_test.go
  • storage/search/lucene/walker.go
📝 Walkthrough

Walkthrough

The PR adds array-field detection and containment rendering for SQL and DynamoDB Lucene drivers. It adds recursive logical traversal, provider-specific SQL behavior, validation errors, execution tests, SQLite support, and array query documentation.

Changes

Array-aware Lucene rendering

Layer / File(s) Summary
Expression traversal and array classification
storage/search/lucene/walker.go, storage/search/lucene/parser.go, storage/search/lucene/*_test.go
Logical expressions are rendered recursively. Slice and array fields are classified as multi-valued fields. Byte slices and nested types remain excluded.
Provider-specific SQL array rendering
storage/search/lucene/sql_driver.go, storage/search/lucene/sql_driver_test.go, storage/search/lucene/sql_executed_test.go, go.mod
SQL rendering supports containment, element wildcards, provider-specific quoting, PostgreSQL casts, null handling, grouped expressions, typed values, and validation errors for unsupported array operators. SQLite execution tests validate generated queries.
DynamoDB array containment
storage/search/lucene/dynamodb_driver.go, storage/search/lucene/dynamodb_driver_test.go
DynamoDB PartiQL uses contains for array equality and grouped values. Typed string, number, and boolean parameters are preserved. Unsupported array operations return errors.
Array query documentation and CI support
docs/lucene.md, .github/workflows/ci.yml
Documentation describes array semantics, provider mappings, limitations, casts, null handling, and operator behavior. CI explicitly enables cgo for SQLite tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • tink3rlabs/magic#227: The PR implements and tests DynamoDB grouped-array containment behavior.
  • tink3rlabs/magic#226: The PR adds SQLite execution coverage for array SQL rendering.

Possibly related PRs

Suggested reviewers: deanefrati

Poem

I’m a rabbit with arrays tucked neat,
Each field now knows the values to meet.
SQL uses containment in its flight,
DynamoDB renders contains right.
Tests guard each element in the strand.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: rendering slice-typed Lucene fields as array containment.
Linked Issues check ✅ Passed The implementation addresses issue [#224] with array containment, element-safe wildcards, provider-specific rendering, and parse errors for unsupported operations.
Out of Scope Changes check ✅ Passed The changes remain within the linked issue scope, including implementation support, provider handling, documentation, CI setup, and regression tests.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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

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

@Lutherwaves
Lutherwaves force-pushed the fix/lucene-array-field-containment branch from fbd0b3b to 6cefdf4 Compare July 31, 2026 17:02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
storage/search/lucene/walker.go (1)

96-101: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Allocate the merged parameter slice explicitly.

append(leftParams, rightParams...) can write into the backing array of leftParams. leftParams comes from renderNode, which builds its slices with append and can therefore return a slice with spare capacity. A future caller that keeps a second slice over the same array would then observe mutated parameters. An explicit allocation removes the aliasing risk at no cost.

♻️ Proposed refactor
-		allParams := append(leftParams, rightParams...)
+		allParams := make([]any, 0, len(leftParams)+len(rightParams))
+		allParams = append(allParams, leftParams...)
+		allParams = append(allParams, rightParams...)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@storage/search/lucene/walker.go` around lines 96 - 101, Update the parameter
merge in the expression-rendering flow around renderNode to allocate a new slice
sized for both leftParams and rightParams, then copy both slices into it instead
of appending directly to leftParams. Continue returning the merged parameters
unchanged for both expr.And and the OR branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/lucene.md`:
- Around line 310-317: The MySQL examples in docs/lucene.md use double-quoted
identifiers instead of the backticks emitted by quoteColumnNameFor("mysql",
...). Update the affected MySQL expressions, including the scalar Equality,
Inclusive range, Exclusive range, Comparison, Wildcard, Has value, and
JSON/array examples, to use backticks for column references while preserving
string literals and SQL behavior.

In `@go.mod`:
- Line 16: Update the CI test job in .github/workflows/ci.yml to set
CGO_ENABLED=1 before running go test -v -cover ./..., ensuring the go-sqlite3
dependency used by sql_executed_test.go can build. Do not change the SQLite
driver.

In `@storage/search/lucene/sql_driver.go`:
- Around line 628-640: Preserve typed containment values across all affected
drivers: in storage/search/lucene/sql_driver.go lines 628-640, update
SQLDriver.renderArrayContains to bind numeric and boolean elements using the
field element type, using JSON_QUOTE only for strings and the appropriate JSON
or numeric casts for MySQL and SQLite; in
storage/search/lucene/dynamodb_driver.go lines 93-97, update containment
serialization to build types.AttributeValue from FieldInfo.Type or explicitly
reject unsupported numeric/boolean arrays. Add numeric and boolean array
coverage to the SQL and DynamoDB array tests.

---

Nitpick comments:
In `@storage/search/lucene/walker.go`:
- Around line 96-101: Update the parameter merge in the expression-rendering
flow around renderNode to allocate a new slice sized for both leftParams and
rightParams, then copy both slices into it instead of appending directly to
leftParams. Continue returning the merged parameters unchanged for both expr.And
and the OR branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e110fba2-8bdd-4e8f-b61e-42035b843c76

📥 Commits

Reviewing files that changed from the base of the PR and between e1b157c and fbd0b3b.

📒 Files selected for processing (10)
  • docs/lucene.md
  • go.mod
  • storage/search/lucene/dynamodb_driver.go
  • storage/search/lucene/dynamodb_driver_test.go
  • storage/search/lucene/parser.go
  • storage/search/lucene/parser_test.go
  • storage/search/lucene/sql_driver.go
  • storage/search/lucene/sql_driver_test.go
  • storage/search/lucene/sql_executed_test.go
  • storage/search/lucene/walker.go

Comment thread docs/lucene.md Outdated
Comment thread go.mod
Comment thread storage/search/lucene/sql_driver.go
Lutherwaves and others added 4 commits July 31, 2026 20:07
golangci-lint errcheck flagged the unchecked return.
Containment values reach the driver already stringified, so binding them
as-is compares a string against a JSON number or boolean and silently
matches nothing. Postgres was covered by the ::type[] cast; MySQL, SQLite
and DynamoDB were not.

- MySQL: CAST(? AS JSON) for numeric/boolean elements. JSON_QUOTE builds a
  JSON string, which never equals a JSON number.
- SQLite: value = json_extract(json(?), '$'). json_each yields numbers as
  numbers and booleans as 1/0.
- DynamoDB: bind N or BOOL attributes instead of stringifying every value
  into an S attribute.

Values are now validated against the element type before rendering, so
nums:abc is a parse error rather than a database error — the same
500-instead-of-400 failure this package exists to remove.

Two adjacent fixes in the same code path:

- Wildcards on non-string arrays are rejected. Postgres errors on ILIKE
  against an integer and MySQL's JSON_SEARCH only inspects string scalars,
  so nums:*5* could only ever error or silently match nothing.
- MySQL array wildcards now fold case on both sides. JSON_SEARCH compares
  under utf8mb4_bin regardless of the column's collation, so tags:*Go* did
  not match ["golang"] — inconsistent with the Postgres and SQLite branches
  and with MySQL's own scalar wildcard branch. This was left unfixed
  earlier only because no MySQL instance was available to verify against.

Verified by execution on all three providers: SQLite via the executed-SQL
tests, MySQL 8.4 and PostgreSQL by running the generated statements with
bound parameters.

Reported by CodeRabbit on #228.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…fier quoting

The MySQL examples used double-quoted identifiers, which MySQL reads as
string literals rather than column references; the driver emits backticks.

Adds a Non-string elements section covering per-provider containment for
numeric and boolean arrays, element-type validation, and why wildcards are
rejected on them. Drops the MySQL array wildcard case-sensitivity
limitation, now fixed.

Reported by CodeRabbit on #228.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The executed-SQL tests use github.com/mattn/go-sqlite3, which needs cgo.
Runners default to CGO_ENABLED=1 so this is not a live failure, but pinning
it keeps those tests from silently failing to build if that default changes.

Reported by CodeRabbit on #228.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@storage/search/lucene/sql_driver.go`:
- Around line 656-668: Include reflect.Uint8 consistently in isNonStringElem and
normalizeArrayElemValue, matching arrayElemCast so named []uint8 slices and
arrays are treated as numeric byte values. Ensure MySQL and SQLite
array-containment bindings receive byte values rather than quoted strings, or
remove the corresponding Uint8 cast branch if Uint8 handling is intentionally
centralized elsewhere.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cdfd61b2-7da9-4972-9054-5630b7d39f0a

📥 Commits

Reviewing files that changed from the base of the PR and between fbd0b3b and 7d5fcf2.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • docs/lucene.md
  • go.mod
  • storage/search/lucene/dynamodb_driver.go
  • storage/search/lucene/dynamodb_driver_test.go
  • storage/search/lucene/parser.go
  • storage/search/lucene/parser_test.go
  • storage/search/lucene/sql_driver.go
  • storage/search/lucene/sql_driver_test.go
  • storage/search/lucene/sql_executed_test.go
  • storage/search/lucene/walker.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • storage/search/lucene/parser_test.go
  • storage/search/lucene/parser.go
  • go.mod
  • docs/lucene.md
  • storage/search/lucene/dynamodb_driver.go
  • storage/search/lucene/walker.go
  • storage/search/lucene/sql_driver_test.go

Comment thread storage/search/lucene/sql_driver.go Outdated
Lutherwaves and others added 2 commits August 1, 2026 00:37
isArrayField excludes any slice or array of bytes, so a uint8 element
cannot reach arrayElemCast. Listing it implied a byte-array code path that
does not exist, and left the kind handling looking inconsistent with
isNonStringElem and normalizeArrayElemValue, which correctly omit it.

Verified: isArrayField reports false for []byte, []uint8, a named []uint8
and [16]byte alike, so no containment binding is affected.

Raised by CodeRabbit on #228, whose reading was that named []uint8 slices
do pass isArrayField; they do not. Removing the dead branch is the other
resolution it offered and the accurate one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ield.go

The per-kind knowledge was spread across four switches — arrayElemCast,
isNonStringElem, elemBitSize and normalizeArrayElemValue — and had already
drifted within this branch: b373e54 removed reflect.Uint8 from
arrayElemCast as unreachable, but elemBitSize still carried it.

Replaces all four with a single arrayElemSpecs table, one row per kind
holding the Postgres cast, the strconv width and whether the kind must not
be bound as a string. Adding or removing a kind is now one edit that cannot
leave the three facts disagreeing.

Moves the dialect-neutral array helpers out of sql_driver.go into a new
arrayfield.go. dynamodb_driver.go consumed four of them, so the SQL driver's
file had become the de-facto home of shared array semantics with no obvious
place for the next one. isArrayField stays in parser.go, where it belongs
beside canUseNestedAccess — the predicate that defines its boundary.

Also:

- Rename the DynamoDB elemType parameters to fieldType. They receive the
  field's type ([]int), not the element's; only the immediate arrayElemKind
  unwrap made them work, so the name would have misled the next caller.
- Extract columnName, the go-lucene column-shape unwrap that had been copied
  into serializeColumn, resolveField and arrayFieldName. A fourth upstream
  shape is now a single edit.
- Rename arrayFieldName to resolveArrayField and return a nil type unless the
  field really is a known array, so the type cannot be read stale.
- Make quoteColumnNameFor a method; every call site passed s.provider.
- Extract the DynamoDB wildcard rejection, duplicated verbatim twice.
- renderArrayWildcard's bare branch returns ref.params instead of nil. Always
  empty today, but only by an invariant stated nowhere.
- Route the last bare-wildcard check through isBareWildcard.
- Document that the walker calls fallback with the parent node, not the
  offending child, and applies no negation wrapping to its result.

Adds arrayfield_test.go pinning the cast mapping, the numeric
classification, value normalization including width range checks, and the
column shapes — the drift guard the four switches lacked.

Behaviour-preserving: the cast table was checked against the switch it
replaces for all 13 kinds plus nil, non-slice, []byte and pointer-to-slice.

Raised by code review of the branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

lucene: slice-typed fields render as scalar equality, producing invalid SQL against array columns

1 participant