Skip to content

test: add differential query compatibility oracle - #84

Merged
viggy28 merged 1 commit into
mainfrom
feat/query-compat-harness
Aug 31, 2026
Merged

test: add differential query compatibility oracle#84
viggy28 merged 1 commit into
mainfrom
feat/query-compat-harness

Conversation

@viggy28

@viggy28 viggy28 commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Summary

Add a purpose-built differential query compatibility harness that treats source Postgres as the oracle and compares it with Streambed through the actual Postgres-wire endpoint.

The suite runs the same deterministic fixture and query matrix against both Iceberg and DuckLake targets. It compares query behavior, column names, Postgres OIDs, nulls, row multiplicity, ordering, and type-aware values.

Closes #78.

Implementation

  • Add test/querycompat with a deterministic Postgres fixture and declarative query cases.
  • Build and start the real Streambed binary for sync and standalone query serving.
  • Wait for the fixture commit LSN instead of using fixed sleeps.
  • Connect to source Postgres and Streambed with pgx simple protocol.
  • Compare integers/text exactly, decimals with exact rational arithmetic, floats with OID-specific tolerances, timestamps by their SQL semantics, timestamptz in UTC, JSON structurally, and UUID/bytea canonically.
  • Run the same cases for Iceberg and DuckLake with explicit supported, unsupported, and known-difference classifications.
  • Add the oracle to pull-request and main-branch CI and to scripts/test-integration.sh.

Compatibility findings fixed

Running the oracle against real services exposed three correctness issues that are fixed here:

  • Advertise standard_conforming_strings=on so pgx can use the simple query protocol.
  • Normalize DuckDB's binary UUID result into canonical Postgres UUID text before pgwire encoding.
  • Write Postgres timestamp to Parquet with isAdjustedToUTC=false, while retaining true for timestamptz; this prevents local timezone shifts when Iceberg is queried.

Known storage-type differences remain explicitly classified for numeric, unconstrained numeric, varchar, JSONB, and Iceberg smallint mapping.

Validation

Started fresh Postgres and MinIO containers, built Streambed, synced the fixture separately to Iceberg and DuckLake, started the Streambed query server, and compared every query through pgwire:

docker compose -f test/integration/docker-compose.yml down -v
docker compose -f test/integration/docker-compose.yml up -d postgres minio --wait
docker compose -f test/integration/docker-compose.yml up createbucket
go test -tags integration -v -count=1 -timeout 10m ./test/querycompat

Result: PASS for Iceberg and DuckLake.

Also ran:

go test ./internal/... ./config/...
go test -race ./internal/server ./internal/parquet
git diff --check
bash -n scripts/test-integration.sh

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b98b0547c6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

syncProcess := startProcess(t, binary, syncArgs...)
t.Cleanup(syncProcess.kill)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stop sync before deferred slot cleanup

If this target fails after the sync process starts but before the explicit stop, this t.Cleanup runs only after runTargetCompatibility's deferred cleanupSource; the slot is therefore still active when cleanup filters on active = false, so it is left behind while its publication is removed. Because every run uses a new slot name and the test Postgres permits only four replication slots, repeated failed local runs can exhaust all slots and prevent subsequent tests from starting. Arrange process termination to run before the database cleanup on every exit path.

Useful? React with 👍 / 👎.

@viggy28
viggy28 merged commit 14cc8a3 into main Aug 31, 2026
4 checks passed
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.

Add a differential query compatibility harness against source Postgres

1 participant