Skip to content

fix: experimental SQL bugs + CI coverage#288

Merged
NikolayS merged 3 commits into
mainfrom
claude/fix-experimental-sql-oqxpbr
Jul 9, 2026
Merged

fix: experimental SQL bugs + CI coverage#288
NikolayS merged 3 commits into
mainfrom
claude/fix-experimental-sql-oqxpbr

Conversation

@NikolayS

Copy link
Copy Markdown
Owner

Bugs

A1 (medium) — pgque.throughput() division by zero for sub-minute buckets.
sql/experimental/observability.sql aligned buckets with % (extract(epoch from i_bucket_size)::int / 60). Integer division makes the divisor 0 for any bucket size under 60 seconds, so select * from pgque.throughput('q', '1 hour', '30 seconds') aborted with division by zero whenever ticks existed in the window. Sizes between 60 s and 120 s that are not minute multiples (e.g. '90 seconds'% 1) silently collapsed to minute boundaries.

A2 (low) — pgque.create_queue(text, jsonb) accepted negative max_retries.
sql/experimental/config_api.sql special-cased 'max_retries' with a direct update pgque.queue, bypassing the >= 0 validation in pgque.set_queue_config(). With '{"max_retries": -1}', the coalesce(ev_retry,0) >= v_max_retries check in pgque.nack() is always true, so every event dead-letters on first nack. History check: the special case landed in the same commit (8a54a7e) that introduced the validating set_queue_config() override, which already accepts max_retries — the special case was redundant, never a workaround.

D2 (low) — experimental SQL had zero CI coverage.
tests/run_experimental.sql was referenced by no workflow, which is exactly how A1/A2 shipped.

Fixes

  • A1: compute buckets in epoch seconds — to_timestamp(floor(extract(epoch from tick_time) / extract(epoch from i_bucket_size)) * ...) — so any positive interval works; reject i_bucket_size <= interval '0' (or null) with a clear exception.
  • A2: route every option through pgque.set_queue_config(), dropping the special case, so negative values are rejected with the canonical error (max_retries must be >= 0).
  • D2: new Run experimental tests step in the test job of .github/workflows/ci.yml (full PG 14–18 matrix): installs sql/pgque.sql plus the three sql/experimental/*.sql files into a separate pgque_experimental database (so the later idempotency/uninstall steps keep testing the default install) and runs tests/run_experimental.sql.

Red/green TDD: tests added first and confirmed failing, then the fixes. New tests:

  • tests/test_observability.sql: throughput with '30 seconds' bucket (was: division by zero), exact 90-second bucketing with synthetic ticks (2 buckets, 100/150 events), non-positive bucket size raises.
  • tests/test_experimental_config_api.sql: create_queue('q', '{"max_retries": -1}') raises the canonical error and the queue is not created.

Verification

Fresh scratch DB, PostgreSQL 16:

createdb pgque_exp
psql -d pgque_exp -v ON_ERROR_STOP=1 -f sql/pgque.sql
psql -d pgque_exp -v ON_ERROR_STOP=1 \
  -f sql/experimental/delayed.sql \
  -f sql/experimental/observability.sql \
  -f sql/experimental/config_api.sql
psql -d pgque_exp -v ON_ERROR_STOP=1 -f tests/run_experimental.sql

Result (tail):

NOTICE:  PASS: throughput() handles 30-second buckets (1 rows)
NOTICE:  PASS: throughput() buckets 90-second intervals from epoch
NOTICE:  PASS: throughput() rejects non-positive bucket size
NOTICE:  PASS: experimental config API
NOTICE:  PASS: create_queue rejects negative max_retries
=== ALL EXPERIMENTAL TESTS PASSED ===

Pre-fix run of the same suite failed with ERROR: division by zero (test 9) and ERROR: create_queue should reject negative max_retries (config test), confirming red before green.

Main suite unaffected — fresh DB:

createdb pgque_all
psql -d pgque_all -v ON_ERROR_STOP=1 -f sql/pgque.sql
psql -d pgque_all -v ON_ERROR_STOP=1 -f tests/run_all.sql

Result: === ALL TESTS PASSED === (exit 0).

sql/experimental/*.sql is not embedded in the generated sql/pgque.sql (verified: no throughput/jsonb create_queue in it), so no rebuild via build/transform.sh was needed.

Addresses findings A1, A2, D2 of #283

https://claude.ai/code/session_01KAaEGkQZmey1D1xCsVGmqv


Generated by Claude Code

@NikolayS

NikolayS commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

samorev Code Review Report

Pipeline Coverage
PASS Not reported

BLOCKING ISSUES (1)

HIGH MR/PR state - Review target is draft

The review target is still marked as draft.
Fix: Mark it ready for review before merge.


Summary

Area Findings Potential Filtered
CI/Pipeline 0 0 0
Security 0 0 0
Bugs 0 0 0
Tests 0 0 0
Guidelines 0 0 0
Docs 0 0 0
Metadata 1 0 0

Note:

  • Findings: High-confidence issues (8-10/10) - blocking or non-blocking per severity
  • Potential: Medium-confidence issues (4-7/10) - review manually
  • Filtered: Low-confidence issues (0-3/10) - excluded as likely false positives
Review metadata
provider=github
kind=pr
project=NikolayS/PgQue
number=288
target=github:NikolayS/PgQue#288
state=OPEN
draft=true
diff_lines=240
diff_added=139
diff_removed=25
diff_bytes=8922
comments_count=0
commits_count=3
ci_status=success
ci_summary=total=14 success=14 failure=0 pending=0 other=0
prompt=.claude/commands/review-mr.md
blocking=false
posted_by=gh
no_comment=false
live_posting=posted

samorev-assisted review (AI analysis by Tanya301/samorev)

@NikolayS NikolayS marked this pull request as ready for review July 8, 2026 21:55
NikolayS and others added 3 commits July 8, 2026 16:51
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NikolayS NikolayS force-pushed the claude/fix-experimental-sql-oqxpbr branch from bf597d1 to 1183a84 Compare July 8, 2026 23:57
@NikolayS

NikolayS commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Deep review + real testing (relocated onto stable/devel split)

Rebased onto current main (post stable/devel split). All of the PR's changes were relocated:
sql/experimental/*.sql fixes moved to devel/sql/experimental/*.sql, and the new CI step installs
devel/sql/pgque.sql + devel/sql/experimental/*.sql. build/transform.sh does not generate the
experimental sources (it only asserts they stay out of the default install), so no regen was needed.
The relocated diff is byte-for-byte the original five-file change, plus one style fix (below).

Review findings

# Sev File:line Finding
1 none (fixed here) devel/sql/experimental/config_api.sql:14 The carried-over 2-line -- comment violates the CLAUDE.md rule (C-style /* */ for 2+ lines). Converted to a block comment in this branch.
2 nit tests/test_observability.sql, tests/test_experimental_config_api.sql Multi-line -- comments in the new tests (e.g. the 3-line "Anchor on a 90-second…" note). Consistent with the existing test-file style; left as-is. Non-blocking.
3 note commits afbdd77/f227910 (now 5162b8e/4e77a96) Each fix commit bundles its test with the fix rather than committing the failing test first. The PR body documents confirmed red-before-green, and I independently reproduced both bugs on pre-fix code (below), so the TDD intent holds; just not visible as separate red/green commits.

Correctness — A1 (throughput() div-by-zero): correct. The guard rejects null/<= interval '0'
before any work; bucketing is now epoch-aligned via to_timestamp(floor(epoch/sec)*sec) and
events_per_sec = events / v_bucket_sec, so no divisor can be zero. Note the intended behavior change:
buckets now align to epoch (UTC) boundaries rather than session-TZ minute boundaries — more correct for
sub-minute and non-minute-multiple sizes.

Correctness — A2 (negative max_retries): correct. The removed else branch was an all-identity
case mapping, so routing every key through pgque.set_queue_config() is behavior-preserving for the
other options and adds the >= 0 validation for max_retries. Atomicity verified: create_queue(text, jsonb)
is one function call, so when set_queue_config() raises mid-loop the whole statement rolls back — the
queue row created by the inner create_queue(i_queue) is gone (empirically count = 0).

Correctness — D2 (CI coverage): the new Run experimental tests step runs under -v ON_ERROR_STOP=1
(and run_experimental.sql sets ON_ERROR_STOP on), so a failing assert/raise fails the job — it
gates. It installs into a separate pgque_experimental DB so the later idempotency/uninstall steps keep
exercising the default install. Runs on the full PG 14–18 matrix.

SECURITY DEFINER / search_path: throughput() retains security definer set search_path = pgque, pg_catalog;
create_queue(text, jsonb) signature/security clause unchanged. Compliant.

Real testing (fresh Docker Postgres 16)

Pre-fix code (main's devel/sql/experimental/*), both bugs reproduced:

-- A1: 3 synthetic ticks + 30s bucket
ERROR:  division by zero
  bucketed as ( … % (extract(epoch from i_bucket_size)::int / 60)) …
-- A2: create_queue('bugq', '{"max_retries": -1}') accepted; queue_max_retries = -1

After the fixes:

-- A1: throughput('bug','1 hour','30 seconds')
      bucket_start      | events |  eps
 2026-07-08 23:51:00+00 |     50 | 1.667
 2026-07-08 23:52:00+00 |     50 | 1.667
-- A2:
ERROR:  set_queue_config: max_retries must be >= 0, got -1
   → queue 'bugq2' not created (count = 0)

Full experimental suite (devel/sql/pgque.sql + experimental + tests/run_experimental.sql):

PASS: throughput() handles 30-second buckets (1 rows)
PASS: throughput() buckets 90-second intervals from epoch
PASS: throughput() rejects non-positive bucket size
PASS: create_queue rejects negative max_retries
=== ALL EXPERIMENTAL TESTS PASSED ===

Default suite unaffected: tests/run_all.sql=== ALL TESTS PASSED ===;
tests/acceptance/run_acceptance.sql=== ALL ACCEPTANCE TESTS PASSED ===.

CI on head 1183a84: all 16 checks green (PG 14–18 matrix, all clients, TLE, pg_cron, pg_timetable,
upgrade, stable smoke). The experimental step's PASS lines and === ALL EXPERIMENTAL TESTS PASSED ===
appear in the test (16) job log, confirming it ran and gated.

Verdict: MERGE-READY.

@NikolayS NikolayS merged commit b11e5f0 into main Jul 9, 2026
16 checks passed
@NikolayS NikolayS deleted the claude/fix-experimental-sql-oqxpbr branch July 9, 2026 00:03
@NikolayS

NikolayS commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

samorev Code Review Report

Pipeline Coverage
PASS Not reported

This report supersedes the earlier samorev review --fetch comment on this PR. That comment was a CI/draft gate check only and performed no code analysis (it hardcoded zeros in every area). The results below come from a genuine per-area review of the diff, verified by reproducing both bugs pre-fix on a fresh PostgreSQL 16 devel build.

No blocking issues. One non-blocking nit below; the fixes are correct and now CI-gated.

What was verified

  • A1 (throughput() div-by-zero): the guard rejects null/<= interval '0' before any work; bucketing is now epoch-aligned via to_timestamp(floor(epoch/sec)*sec) and events_per_sec = events / v_bucket_sec, so no divisor can be zero. Reproduced div-by-zero on pre-fix code; fixed output verified. (Intended behavior change: buckets now align to epoch/UTC boundaries rather than session-TZ minute boundaries — more correct for sub-minute and non-minute-multiple sizes.)
  • A2 (negative max_retries): the removed else branch was an all-identity case, so routing every key through pgque.set_queue_config() is behavior-preserving and adds the >= 0 validation. Atomicity verified: create_queue(text, jsonb) is one call, so a mid-loop raise rolls back the whole statement — the queue row is gone (count = 0). Reproduced acceptance of -1 pre-fix; post-fix raises and creates nothing.
  • D2 (CI coverage): the new Run experimental tests step runs under ON_ERROR_STOP=1 (and run_experimental.sql sets it on), installs into a separate pgque_experimental DB, and runs on the full PG 14–18 matrix — a failing assert/raise fails the job. Confirmed the PASS lines + === ALL EXPERIMENTAL TESTS PASSED === appear in the test (16) job log.
  • Security definer: throughput() keeps security definer set search_path = pgque, pg_catalog; create_queue(text, jsonb) clause unchanged. Default suite unaffected (run_all.sql + acceptance green). CI 16/16.

NON-BLOCKING (1)

INFO tests/test_observability.sql, tests/test_experimental_config_api.sql - multi-line -- comments in the new tests

CLAUDE.md prefers C-style /* ... */ for comments spanning 2+ lines (e.g. the 3-line "Anchor on a 90-second…" note).
Suggestion: Consistent with the existing test-file style, so left as-is is defensible. Cosmetic.

Notes (not findings): the carried-over 2-line -- comment in devel/sql/experimental/config_api.sql:14 was already converted to a block comment in this branch. The fix commits bundle each test with its fix rather than committing the failing test first, but the PR body documents red-before-green and both bugs were independently reproduced on pre-fix code, so the TDD intent holds.


Summary

Area Findings Potential Filtered
CI/Pipeline 0 0 0
Security 0 0 0
Bugs 0 0 0
Tests 0 0 0
Guidelines 1 0 0
Docs 0 0 0
Metadata 0 0 0

Note:

  • Findings: High-confidence issues (8-10/10) - blocking or non-blocking per severity
  • Potential: Medium-confidence issues (4-7/10) - review manually
  • Filtered: Low-confidence issues (0-3/10) - excluded as likely false positives
Review metadata
provider=github
kind=pr
project=NikolayS/pgque
number=288
target=github:NikolayS/pgque#288
state=OPEN
draft=false
diff_lines=242
diff_added=141
diff_removed=25
diff_bytes=9023
comments_count=1
commits_count=3
ci_status=success
ci_summary=total=16 success=16 failure=0 pending=0 other=0
prompt=.claude/commands/review-mr.md
blocking=false
posted_by=gh
no_comment=false
live_posting=posted

samorev-assisted review (AI analysis by Tanya301/samorev)

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