Skip to content

Fix runtime string predicates and PostgreSQL native parameter binding - #112

Merged
Dastari merged 4 commits into
mainfrom
fix/runtime-string-bindings-20261006
Oct 12, 2026
Merged

Dastari merged 4 commits into
mainfrom
fix/runtime-string-bindings-20261006

Conversation

@Dastari

@Dastari Dastari commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Runtime EndsWith("") returned only the empty SQLite value, and PostgreSQL prefix/suffix predicates failed because driver normalization split repeated native parameters into different slots. This independent fix makes empty operands match all non-null strings and preserves complete-statement PostgreSQL native binding identity.

The base is df474c761c4057cb643e27a6582dd0376f56e31f, the source of published workspace-2026.10.03.2 (ORM/macros 0.39.0). Main and the latest published release were rechecked on October 6; neither contains this fix. This branch aligns ORM/macros at 0.39.1 (macros alignment only).

Compatibility

  • No public signature, GraphQL SDL, runtime cursor format, schema/default/storage change or data migration.
  • Prefix/suffix/contains are case-sensitive literal operations. Empty operands include every non-null string, including empty values. SQL NULL remains unknown under comparisons and NOT; explicit is_null can include it. Unicode and literal %, _, backslash and placeholder-shaped values stay bound parameters.
  • At PostgresBackend::normalize_sql, native-only complete statements preserve $n identity and repeated references. Positive start N adds N−1 to each index. Native out-of-order references also retain their true slots. This intentional correction removes reliance on occurrence order for native statement indices.
  • Anonymous/mixed forms retain existing occurrence rebasing. SqlDialect::normalize_sql keeps static fragment rebasing: generated fragments can use arbitrary indices or restart numbering for separate values. Changing that behavior would break existing generated filters. Tests cover both contracts and nested generated clauses.
  • All seven PostgreSQL normalization call sites share the corrected backend boundary: plain/authenticated reads, pinned read/count pairs, executor reads, pool writes and executor writes. Transaction adapters use these paths. MSSQL dialect/driver paths and runtime capability gates remain unchanged.
  • Runtime host authorization/projection/limits, current PostgreSQL RLS, keyset ordering/counts and safe errors remain in force. Separate page requests still do not pin a snapshot. Embedded U+0000 remains outside portable TEXT semantics.
  • Independent of runtime-platform A–D. PR A and unrelated worktrees are untouched. No sibling repository changes.

Verification

Final base: df474c761c4057cb643e27a6582dd0376f56e31f.
Final head: 01d4134da18028ec58e318e0ffd30ab244d0d8e1.
ORM/macros: 0.39.1, required lane features sqlite or postgres with default features disabled. Rust 1.97.1. Disk-backed scratch/build caches, CARGO_BUILD_JOBS=2, sequential backend builds, debug line-tables-only, incremental disabled. No workspace all-features command.

Before production edits, the baseline SQLite seven-row fixture failed with empty-suffix count 1 instead of 7; PostgreSQL runtime execution failed safely as backend_execution, and the native-repeat test produced SELECT $1, $2 instead of SELECT $1, $1. The corrected first seven non-null rows return prefix π = 2, suffix foo = 1, empty prefix/suffix = 7 on both backends. Extended execution covers Unicode/emoji/newline, case, literal %/_/backslash/quotes/placeholder-shaped data, longer operands, SQL NULL and explicit null inclusion, AND/OR/NOT, separately bound visibility, both keyset directions, tied keys and matching total counts. Invalid cursors are rejected against a closed pool before I/O; safe errors omit caller values.

PostgreSQL exercises plain driver reads, transaction executor reads, pool/executor writes with repeated parameters, authenticated read/count pairs, counted/uncounted runtime reads, and a verified nonowner/NOBYPASSRLS role. Current tenant authorization applies before pages/counts, different identities see their own partitions, and transaction-local settings do not leak into subsequent unauthenticated requests. SQLite/PostgreSQL no-op replans after reads prove the physical target is unchanged. Static nested generated filters preserve fragment rebasing.

Audit: the seven PostgreSQL normalization call sites use this complete-statement boundary; transaction adapters delegate to those executor paths. Runtime relation batch row/count rendering also calls the shared RuntimePredicate::render (runtime_relations.rs), and uses the same authenticated backend paths. Existing SQLite relation batching tests passed; no separate live PostgreSQL relation-predicate conformance is claimed here.

Actual commands/results (each cargo command used the two-job disk-cache environment above):

cargo test -p graphql-orm --locked --no-default-features --features sqlite
# PASS; 415 passed / 0 failed / 11 ignored across logged root + child fixture suites.
# Ignored: large benchmark and 10 existing example doctests; not backend execution gates.
cargo test -p graphql-orm --locked --no-default-features --features postgres --lib --test runtime_strings --test query_ir --test runtime_records --test runtime_schema_ir --test portable_group_pages -- --include-ignored --test-threads=1
# PASS; 85 scoped tests, 0 ignored (includes unit/rendering/validation plus owned live PostgreSQL).
cargo test -p graphql-orm --locked --no-default-features --features mssql --lib --test query_ir --test runtime_records
# PASS; 55 tests, 0 ignored; compilation/rendering/capability evidence, no live MSSQL.
cargo test --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --all-targets
cargo run --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --example runtime_strings
cargo test --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features postgres --test runtime_strings -- --ignored --test-threads=1
# PASS: external SQLite compile/tests + actual example execution; one explicit live PostgreSQL example test, 0 skipped.
# Normal consumer dependencies remain graphql-orm and serde only, without direct async-graphql.
# PostgreSQL/MSSQL consumer --all-targets compilation also passed.
# MSSQL consumer check retains an existing unused Backend import warning in host_timestamps; not broadened here.
cargo test -p graphql-orm-macros --locked --no-default-features --features sqlite
cargo test -p graphql-orm-macros --locked --no-default-features --features postgres
cargo test -p graphql-orm-macros --locked --no-default-features --features mssql
# PASS each: 9 unit tests; 6 existing doctests ignored.
cargo clippy -p graphql-orm -p graphql-orm-macros --locked --no-default-features --features sqlite --all-targets -- -D warnings
cargo clippy -p graphql-orm -p graphql-orm-macros --locked --no-default-features --features postgres --lib --test runtime_strings --test query_ir --test runtime_records --test runtime_schema_ir -- -D warnings
cargo clippy -p graphql-orm -p graphql-orm-macros --locked --no-default-features --features mssql --lib --test query_ir --test runtime_records -- -D warnings
# PASS all; external consumer --all-targets warnings-denied Clippy passed for SQLite and PostgreSQL.
# Scoped PostgreSQL/MSSQL lanes avoid the separately tracked broader target-gating defects.
# For each of sqlite/postgres/mssql, sequentially:
RUSTDOCFLAGS="-D warnings" cargo doc -p graphql-orm -p graphql-orm-macros --locked --no-default-features --features "$backend" --no-deps
# PASS all three.
cargo fmt --all -- --check
cargo fmt --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml -- --check
python3 scripts/check-documentation.py --base df474c761c4057cb643e27a6582dd0376f56e31f
python3 scripts/generate-workspace-inventory.py --check
scripts/check-workspace-dependencies.sh
scripts/check-package-release-policy.sh df474c761c4057cb643e27a6582dd0376f56e31f
scripts/check-semver.sh df474c761c4057cb643e27a6582dd0376f56e31f
python3 scripts/check-release-state.py
scripts/check-release-manifest.sh
python3 scripts/test-release-manifest.py
python3 scripts/test-router-notices.py
python3 scripts/check-pr-documentation-impact.py
# PASS; PR_BODY supplied for the documentation-impact check.

PostgreSQL tests own loopback Docker PostgreSQL 17 containers and verify immutable container ID/ownership before cleanup; the new fixtures additionally check database/user identity. SQLite uses exclusively owned in-memory pools and verifies no on-disk path before close. Ambient database URLs are refused. The updated CI, release validation and owned-backend scripts are configured to execute the normally ignored PostgreSQL runtime/consumer tests.

The standalone runtime string example uses existing schema-bound APIs, current structural visibility and private projection without handwritten query SQL. Core runtime APIs/cursor formats are preserved. This is an upstream release candidate, not downstream adoption: Digibase remains unchanged, and its ignored release gates were not run or counted as passing.

Documentation impact

  • Documentation updated
  • No documentation impact

Updated the canonical runtime-query reference with exact empty/null/string behavior, complete-statement versus fragment binding, provider limitations, examples and verification commands. Updated both package READMEs, CHANGELOG, MIGRATION, aligned manifests/locks and generated workspace inventory. No accepted ADR or runtime-platform proposal changes.

No merge or release has been performed for this fix. Owner review/authorization is required; downstream adoption follows an immutable reviewed release and consumer-owned backend gates.

Hosted verification: the owner paused GitHub Actions during final handoff. Eleven checks passed; the companions check was canceled after the owner paused Actions. The run is completed with a canceled conclusion. No reruns, dispatches or workflow enablement will be performed. Local verification above completed independently.

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