Skip to content

feat: functional/expression indexes via override_expression - #153

Open
arreyder wants to merge 2 commits into
mainfrom
crr/index-override-expression
Open

feat: functional/expression indexes via override_expression#153
arreyder wants to merge 2 commits into
mainfrom
crr/index-override-expression

Conversation

@arreyder

Copy link
Copy Markdown
Contributor

What & why

Adds an override_expression field to the pgdb.v1.MessageOptions.Index option so proto authors can declare functional/expression indexes — e.g. a btree over a JSONB path. Today the only way to index a value derived from a JSONB/proto-map field is to denormalize it into a real column (proto field + write-path + backfill migration + Dynamo re-mirror). This exposes a capability the DDL layer already has.

Motivating case: SearchUserOwnership filters (role_to_entitlement_id ->> 'primary') IN (...) with no serving index (the only index on that column is GIN/containment). With this, the fix is one index declaration:

option (pgdb.v1.msg).indexes = {
  name: "primary_owner"
  method: INDEX_METHOD_BTREE
  partial_deleted_at_is_null: true
  override_expression: "tenant_id, ((\"pb$role_to_entitlement_id\" ->> 'primary'))"
};

How

  • index2sql already renders Index.OverrideExpression verbatim (HNSW vector indexes use it). This just threads the new proto field through extraIndexes into that existing path. When set, columns is ignored for DDL (matching the vector precedent); partial_deleted_at_is_null still applies.
  • Bug fix: the descriptor template interpolated OverrideExpression unescaped ("{{ .DB.OverrideExpression }}"). Any expression containing a quoted identifier produced invalid Go (missing ',' in composite literal). Now emitted via %q. Vector overrides never tripped this (no quotes); expression indexes do.

Author contract

The override body is emitted verbatim, so it carries physical column tokens: proto fields are pb$-prefixed and quoted, system columns are bare — same convention the vector overrides already use.

Validation

  • New DB-free regression test (TestIndexOverrideExpression) asserts the rendered CREATE INDEX (verbatim body + preserved partial predicate).
  • Existing TestSchemaPet now exercises the new Pet functional index against a real Postgres.
  • Manually verified the generated DDL executes on Postgres 16 and the planner uses the index (Index Scan).

Diff shape

Core change is ~16 lines (proto field + one passthrough + template escape). The rest is regenerated .pb.go / example output.

🤖 Generated with Claude Code

arreyder and others added 2 commits July 14, 2026 18:10
Add an `override_expression` field to the `pgdb.v1.MessageOptions.Index`
option. When set, it replaces the "(column, ...)" body of the generated
CREATE INDEX with raw SQL, enabling functional/expression indexes — e.g. a
btree over a JSONB path:

  option (pgdb.v1.msg).indexes = {
    name: "primary_owner"
    method: INDEX_METHOD_BTREE
    partial_deleted_at_is_null: true
    override_expression: "tenant_id, ((\"pb$role_to_entitlement_id\" ->> 'primary'))"
  };

The DDL renderer already honored Index.OverrideExpression (HNSW vector
indexes use it); this exposes it to proto authors for any index method.
`columns` is ignored for DDL when an override is set, matching the vector
precedent. Partial predicates (partial_deleted_at_is_null) still apply.

Also escape the emitted OverrideExpression literal with %q in the descriptor
template. It was previously interpolated unescaped, which produced invalid Go
("missing ',' in composite literal") for any expression containing a quoted
identifier. Vector overrides never tripped this; expression indexes do.

Adds a Pet example index and a DB-free regression test asserting the rendered
DDL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…visible test

Review follow-ups to the override_expression feature:

- Physical columns are ALL pb$-prefixed (including tenant_id/deleted_at), so
  the example override and the doc comment were wrong — bare `tenant_id`
  generates `column "tenant_id" does not exist`. Corrected to "pb$tenant_id"
  and fixed the proto doc ("system columns are bare" was false).
- Reject bit_hamming_ops + override_expression together (both write
  OverrideExpression; silent last-writer-wins). This also removes the
  Columns[0] out-of-range path for an override-only index with no columns.
- Add a renderer-level index2sql test in pgdb/v1 (the root module CI actually
  runs; the example/ module is a nested module excluded by `go test ./...`).
- Update TestSchemaPet migration count (dropping pb$profile now cascades to
  two dependent indexes, not one).

Co-Authored-By: Claude Opus 4.8 (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.

1 participant