Skip to content

SPOC-542: add snowflake-based distributed sequence access method#479

Open
danolivo wants to merge 1 commit into
mainfrom
spoc-542
Open

SPOC-542: add snowflake-based distributed sequence access method#479
danolivo wants to merge 1 commit into
mainfrom
spoc-542

Conversation

@danolivo
Copy link
Copy Markdown
Contributor

Summary

Add a distributed-sequence framework to Spock with a transparent Snowflake-based generator. Application code, serial defaults, GENERATED AS IDENTITY columns, and trigger-driven nextval() calls all benefit without changes: the in-core nextval_hook (added by the included PG core patch) routes per-sequence to a Snowflake method whose 64-bit values are cluster-wide unique without coordinator round-trips.

What changes

PostgreSQL core (patches/{15,16,17,18}/pgNN-050-nextval-hook.diff) — adds a single nextval_hook function pointer to nextval_internal() and factors a sequence_am_local_nextval() helper out of the existing body. Hook signature is identical to Paquier's in-flight SeqAM nextval callback (SequenceAmRoutine.nextval); when his patch lands, the call-site swap to rel->rd_sequenceam->nextval(...) is mechanical. Per-major patch files cover PG 15/16/17/18, ~172 lines each, each forward- and reverse-applies cleanly against its REL_NN_STABLE branch.

Catalog (spock.sequence_kind) — name-keyed (nspname, relname) registry mapping sequences to generation methods (local or snowflake). Registered with pg_extension_config_dump so initial subscription sync carries rows to the destination node.

Dispatcher (src/spock_seqam.c) — attached to nextval_hook in _PG_init; resolves the kind for each sequence via a backend-local HTAB cache, dispatches to a per-kind method. Probe-then-populate pattern means the relcache invalidation callback can free entries directly without dangling-pointer hazards from in-flight dispatcher frames.

Snowflake method (src/spock_seqam_snowflake.c) — bit layout [41-bit ms-since-2023-01-01-UTC | 10-bit node_id | 12-bit counter]. Per-sequence state lives in the sequence relation's own heap tuple (last_value, with node_id bits masked off on read); no DSA / dshash / extension-private shared memory. Buffer-lock concurrency model identical to stock sequences. Crash recovery via the standard XLOG_SEQ_LOG machinery; SEQ_MAGIC page-magic validated on every read; stale xmax (pre-PG-12 SELECT-FOR-UPDATE class) defensively cleared.

Time-domain WAL batchingseq->log_cnt repurposed as a future-dated reservation threshold. We pre-log a 30-ms window of generation and skip WAL on calls within it; crash recovery restores the reservation so post-crash emissions are strictly greater than any pre-crash emission (timestamp may jump forward up to 30 ms; no duplicates are possible). ~30× WAL volume reduction at typical rates relative to "one record per call". Same idea core PG us

@danolivo danolivo self-assigned this May 21, 2026
@danolivo danolivo added the feature New feature label May 21, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a pluggable PostgreSQL nextval() hook and a Spock SeqAM dispatcher with per-sequence "kinds" stored in spock.sequence_kind. Provides SQL management functions, integrates hooks for DROP/ALTER, skips managed sequences during replication, and implements a Snowflake SeqAM producing per-node-unique 64-bit values (timestamp/node-id/counter).

Changes

Distributed Snowflake Sequences

Layer / File(s) Summary
PostgreSQL nextval() hook mechanism
patches/15/pg15-050-nextval-hook.diff, patches/16/pg16-050-nextval-hook.diff, patches/17/pg17-050-nextval-hook.diff, patches/18/pg18-050-nextval-hook.diff
Adds a global nextval_hook and factors built-in generation into sequence_am_local_nextval() so per-call overrides can implement custom SeqAMs.
Spock SeqAM types and public API headers
include/spock_seqam.h
Defines SpockSeqAmKind, SpockSeqAmMethod, Snowflake layout/constants, GUC/default, module init and catalog helper prototypes, and SQL-callable function declarations.
Node ID derivation for Snowflake encoding
include/spock_node.h, src/spock_node.c
Adds spock_node_id_for_name() to derive a stable node Oid from node name for embedding into Snowflake IDs.
SeqAM dispatcher, catalog and management
src/spock_seqam.c, src/spock.c
Implements spock_seqam_nextval() with a per-backend (seqoid->kind) cache and relcache invalidation, module init (GUC and hook installation), catalog maintenance helpers (drop/relocate), and SQL functions alter_sequence_set_kind, convert_all_sequences, and sequence_hook_available.
Snowflake sequence value generator
src/spock_seqam_snowflake.c
Implements spock_seqam_snowflake_nextval() producing monotonic per-node Snowflake values, handling clock regression, counter exhaustion, WAL reservation, and returning *last for session reservations.
Integration with replication, DDL, and compat
src/spock_apply.c, src/spock_executor.c, src/spock_sequences.c, src/compat/{15,16,17,18}/spock_compat.h
Skips managed sequences during synchronization, avoids applying setval() to managed sequences, updates spock.sequence_kind on DROP/ALTER via object-access hooks, and removes obsolete Form_pg_sequence compat macros.
SQL schema, catalog table and wiring
sql/spock--6.0.0.sql, mkdocs.yml
Adds spock.sequence_kind table and index, wires C-backed SQL functions, provides spock.sequence_info view, and updates mkdocs navigation.
User documentation
docs/distributed_sequences.md
Adds detailed documentation describing distributed sequences, Snowflake layout, guarantees, SQL surface, operational procedures, security model, monitoring guidance, and limitations.
Regression tests and test schedule
tests/regress/sql/init.sql, tests/tap/schedule
Guards init SQL for compatibility shims, adjusts extension-check query, and enables the Snowflake SeqAM regression test in the TAP schedule.

"I hop and hash and craft your id,
Ten bits of me tucked safely inside,
Timestamps march, counters spin with glee,
Across the cluster — unique as can be,
A rabbit's wink for distributed decree." 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature addition: a snowflake-based distributed sequence access method, which aligns with the substantial changes across multiple files and the core functionality introduced.
Description check ✅ Passed The description relates to the changeset by explaining the distributed-sequence framework and Snowflake-based generator implementation details, matching the files modified and objectives outlined.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch spoc-542

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 and usage tips.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented May 21, 2026

Not up to standards ⛔

🔴 Issues 1 critical · 1 high · 10 medium

Alerts:
⚠ 1 issue (≤ 0 issues of at least critical severity)
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
12 new issues

Category Results
ErrorProne 1 high (1 false positive)
Security 1 critical (1 false positive)
Complexity 10 medium

View in Codacy

🟢 Metrics 137 complexity · 4 duplication

Metric Results
Complexity 137
Duplication 4

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (6)
tests/regress/sql/seqam_snowflake.sql (1)

185-188: 💤 Low value

Direct DELETE FROM spock.sequence_kind bypasses the dispatcher invalidation contract.

The documentation/dispatcher design relies on relcache invalidation triggered through spock.alter_sequence_set_kind() to flush per-backend dispatcher caches; a raw DELETE against the catalog table skips that path. For an end-of-file cleanup in a single-backend regression this is unlikely to cause observable issues, but it sets a poor example. Prefer iterating with alter_sequence_set_kind(..., 'local') (or simply rely on the DROP SEQUENCE cascade if the executor hook removes the row) so the cleanup matches the supported surface.

🤖 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 `@tests/regress/sql/seqam_snowflake.sql` around lines 185 - 188, Replace the
raw catalog deletion with the supported dispatcher-invalidation calls: instead
of "DELETE FROM spock.sequence_kind" iterate over the sequence entries (e.g.,
local_seq, other_seq, sf_seq) and call
spock.alter_sequence_set_kind(sequence_regclass, 'local') for each to trigger
relcache invalidation; alternatively rely on the existing DROP SEQUENCE
statements to cascade the cleanup if the executor hook removes the
spock.sequence_kind rows. Ensure you use spock.alter_sequence_set_kind() rather
than direct manipulation of spock.sequence_kind to preserve the invalidation
contract.
docs/distributed_sequences.md (1)

142-148: 💤 Low value

Add a language to the fenced code block (markdownlint MD040).

✏️ Proposed fix
-```
+```text
  bit 63        : reserved, always 0 (sign bit, keeps values non-negative)
🤖 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 `@docs/distributed_sequences.md` around lines 142 - 148, The fenced code block
showing the bit layout needs a language tag to satisfy markdownlint MD040;
update the opening fence for that block (the triple backticks before "bit 63
...") to include a language identifier such as "text" or "txt" so it becomes
"```text", leaving the block contents unchanged.
src/spock_executor.c (2)

239-259: 💤 Low value

Consider reusing outer-scope variables to avoid shadowing.

Lines 250-251 declare Oid save_userid; int save_sec_context; in the inner scope, but these shadow the already-declared variables at lines 205-206. While not incorrect, reusing the outer declarations would improve readability and reduce cognitive load for maintainers.

♻️ Optional refactor to reuse outer-scope variables
 		if (get_rel_relkind(objectId) == RELKIND_SEQUENCE &&
 			!dropping_spock_obj)
 		{
-			Oid	save_userid;
-			int	save_sec_context;
-
 			GetUserIdAndSecContext(&save_userid, &save_sec_context);
 			SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
 								   save_sec_context |
🤖 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 `@src/spock_executor.c` around lines 239 - 259, The inner block in the
sequence-drop handling shadows outer variables save_userid and save_sec_context;
remove the inner declarations so the block reuses the outer-scope save_userid
and save_sec_context variables (keep the calls to GetUserIdAndSecContext,
SetUserIdAndSecContext and the spock_seqam_drop_sequence_record(objectId) call
intact) to avoid variable shadowing and improve readability around the
RELKIND_SEQUENCE check and sequence cleanup logic.

295-316: 💤 Low value

Consider reusing outer-scope variables to avoid shadowing.

Lines 307-308 declare Oid saved_userid; int saved_sec_context; in the OAT_POST_ALTER branch, but these shadow the variables declared at lines 205-206 (though with slightly different names: saved_* vs save_*). Reusing the outer declarations would improve consistency and readability.

♻️ Optional refactor to reuse outer-scope variables
 	else if (access == OAT_POST_ALTER && subId == 0 &&
 			 classId == RelationRelationId &&
 			 get_rel_relkind(objectId) == RELKIND_SEQUENCE)
 	{
-		Oid		saved_userid;
-		int		saved_sec_context;
-
-		GetUserIdAndSecContext(&saved_userid, &saved_sec_context);
+		GetUserIdAndSecContext(&save_userid, &save_sec_context);
 		SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
-							   saved_sec_context |
+							   save_sec_context |
 							   SECURITY_LOCAL_USERID_CHANGE);
 		spock_seqam_relocate_sequence_record(objectId);
-		SetUserIdAndSecContext(saved_userid, saved_sec_context);
+		SetUserIdAndSecContext(save_userid, save_sec_context);
 	}
🤖 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 `@src/spock_executor.c` around lines 295 - 316, Remove the inner declarations
that shadow the outer saved user/context variables and reuse the existing
outer-scope variables instead: in the OAT_POST_ALTER branch drop the lines
declaring "Oid saved_userid; int saved_sec_context;" and call
GetUserIdAndSecContext, SetUserIdAndSecContext and the
spock_seqam_relocate_sequence_record(objectId) using the previously declared
outer variables (e.g., save_userid/save_sec_context or the existing saved_*
identifiers) so you don't shadow the outer scope and keep naming consistent.
sql/spock--6.0.0.sql (1)

405-411: ⚡ Quick win

Consider making the view more robust to stale OIDs.

The sequence_info view casts sk.seqoid::regclass at line 407. After a logical restore, stale OIDs may no longer reference valid sequences, causing the cast to ERROR and making the entire view unusable.

Suggestion:
Add a LEFT JOIN to pg_class and filter or mark invalid entries, so the view remains queryable even when OIDs are stale:

♻️ Proposed robustness improvement
 CREATE VIEW spock.sequence_info AS
 SELECT
-	sk.seqoid::regclass AS sequence_name,
+	CASE WHEN c.oid IS NOT NULL 
+	     THEN sk.seqoid::regclass 
+	     ELSE NULL 
+	END AS sequence_name,
 	sk.kind,
 	CASE WHEN spock.sequence_hook_available() THEN 'active'
 	     ELSE 'inactive' END AS hook_status
-FROM spock.sequence_kind sk;
+FROM spock.sequence_kind sk
+LEFT JOIN pg_class c ON c.oid = sk.seqoid AND c.relkind = 'S';

Alternatively, document that operators must run spock.convert_all_sequences() immediately after pg_restore to refresh OIDs before querying the view.

🤖 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 `@sql/spock--6.0.0.sql` around lines 405 - 411, The view spock.sequence_info
currently uses sk.seqoid::regclass (in the CREATE VIEW spock.sequence_info
SELECT block), which will ERROR on stale OIDs after logical restore; modify the
view to LEFT JOIN pg_class (join pg_class c ON c.oid = sk.seqoid) and use
c.relname (or construct schema-qualified name from pg_namespace + relname) to
present sequence_name while marking or filtering rows where c.oid IS NULL as
invalid/stale (e.g., set sequence_name to NULL or 'INVALID_OID'); keep the
existing spock.sequence_hook_available() usage for hook_status and document or
leave a note to run spock.convert_all_sequences() after pg_restore if users
prefer to refresh OIDs.
src/spock_seqam.c (1)

841-846: ⚡ Quick win

Stale seqmax bound in the header comment for validate_sequence_target.

The comment states the snowflake path refuses seqmax < 1 << 22, but the actual check at line 900 is maxv < PG_INT64_MAX (requires NO MAXVALUE). The implementation is far stricter than the doc claims, which is misleading for anyone reading the header to understand the validation contract.

📝 Suggested comment correction
- *   - seqmax < 1 << 22 (any non-zero snowflake timestamp shifts left
- *     22 bits; smaller MAXVALUE excludes the snowflake range),
+ *   - seqmax < PG_INT64_MAX (snowflake emissions use the full
+ *     non-negative int64 range up to year ~2092, so any user-imposed
+ *     MAXVALUE would eventually be violated; require NO MAXVALUE),
🤖 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 `@src/spock_seqam.c` around lines 841 - 846, Header comment for
validate_sequence_target is misleading: it says SPOCK_SEQAM_SNOWFLAKE refuses
seqmax < 1 << 22 but the code actually requires NO MAXVALUE (maxv <
PG_INT64_MAX). Update the header to match the implementation by replacing the
"seqmax < 1 << 22" clause with a statement that snowflake mode requires no
MAXVALUE (i.e., maxv == PG_INT64_MAX or equivalent), and mention the actual
check symbol names SPOCK_SEQAM_SNOWFLAKE, validate_sequence_target, maxv,
PG_INT64_MAX so readers can correlate comment to the code paths.
🤖 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/distributed_sequences.md`:
- Around line 156-159: The "Monotonicity per node" bullet is inaccurate about
state storage; update the wording to say that per-sequence state is persisted in
the sequence relation's heap tuple (the last_value column) and protected by WAL
pre-log reservation rather than stored in extension-private shared memory, and
clarify that backend restarts read last_value from the heap tuple to continue
monotonic issuance (reference symbols: last_value, sequence relation heap tuple,
WAL pre-log reservation, and the "Monotonicity per node" bullet).
- Around line 270-278: The numbered list in docs/distributed_sequences.md has a
duplicate "3." causing the sequence to read 1, 2, 3, 3; update the final list
item to "4." so the steps read 1, 2, 3, 4 — specifically edit the line
describing the new node generating Snowflake values on first `nextval()` call
(following `spock.preview_snowflake_node_id(...)` and `spock.node_create(...)`)
and change its leading ordinal from "3." to "4.".

In `@sql/spock--6.0.0.sql`:
- Around line 332-334: The comment/doc mismatch: spock.sequence_kind is
described as node-local and claims convergence via re-executing
spock.alter_sequence_set_kind() on each node via AutoDDL, but the current
spock_autoddl_process only replicates LOGSTMT_DDL utility statements and does
not handle spock.alter_sequence_set_kind(), while src/spock_seqam.c and
docs/distributed_sequences.md imply AutoDDL-driven propagation; to fix, choose
one of two approaches and implement it consistently: (A) implement AutoDDL
replay for spock.alter_sequence_set_kind() by ensuring the function invocation
is logged as LOGSTMT_DDL and adding replay/handling in spock_autoddl_process
(and update src/spock_seqam.c and docs/distributed_sequences.md to state
cluster-wide propagation), or (B) update the SQL comment, src/spock_seqam.c, and
docs/distributed_sequences.md to explicitly document that spock.sequence_kind is
node-local, that operators must run spock.alter_sequence_set_kind() or
spock.convert_all_sequences() on each node manually (and describe the behavior
if only one node is changed), choosing the option that matches intended design.

In `@src/spock_seqam.c`:
- Around line 1090-1101: The reset currently always calls
seqam_reset_sequence_data() when kind != SPOCK_SEQAM_LOCAL, which wipes existing
snowflake state even if the row already had the same kind; change the guard to
only reset when we are actually transitioning kinds: in alter_sequence_set_kind,
use the previously-read found and row_kind values (from the earlier branch that
fetched the existing row) and call seqam_reset_sequence_data(seqrel) only when
kind != SPOCK_SEQAM_LOCAL AND (NOT found OR row_kind != kind), so existing
sequences with the same kind are not reset while new inserts or actual kind
changes still trigger the reset.

In `@tests/regress/sql/seqam_snowflake.sql`:
- Around line 18-26: The test fails because snowflake sequences need a
configured Spock node; either call spock.node_create('your_node_name') near the
top so spock.preview_snowflake_node_id('your_node_name') yields the expected 42,
or change the assertion that sets decoded_node_correct (currently comparing
node_id = 42) to compute the expected node id via
spock.preview_snowflake_node_id(current_node_name) instead of hard-coding 42;
specifically update the header to create a node with spock.node_create(...)
and/or replace the literal 42 in decoded_node_correct with
spock.preview_snowflake_node_id(...) so nextval() calls succeed and the test
produces deterministic output.

In `@tests/tap/t/050_seqam_snowflake.pl`:
- Around line 8-35: Update the test header in 050_seqam_snowflake.pl to reflect
the current Snowflake design: state is stored in the sequence heap tuple (not a
separate shmem slot allocator), the per-node ID is derived from the node name
(not spock.snowflake_node_id), and the test exercises sequence heap-backed
Snowflake state and spock.sequence_kind replication rather than any shmem
allocator; keep references to the higher-level mechanisms under test
(nextval_hook, dispatcher, snowflake CAS loop) but remove or replace the
incorrect lines about a shmem slot allocator and spock.snowflake_node_id setup
so the header accurately documents the implementation.
- Around line 46-63: The test currently writes the removed GUC
spock.snowflake_node_id via set_snowflake_node_id and restarts, which causes
startup failures; instead change the setup to derive per-node 10-bit snowflake
ids at runtime using spock.preview_snowflake_node_id(node_name) (callable from
the test via scalar_query), create nodes with spock.node_create(node_name, ...)
using those node names, replace the hard-coded 101/202 assertions with the
derived ids captured into variables (via scalar_query) and use those variables
in assertions, and replace any use of current_setting('spock.snowflake_node_id')
in INSERT payloads with either a literal node tag or
current_setting('spock.node_name', true) so no removed GUC is referenced.

---

Nitpick comments:
In `@docs/distributed_sequences.md`:
- Around line 142-148: The fenced code block showing the bit layout needs a
language tag to satisfy markdownlint MD040; update the opening fence for that
block (the triple backticks before "bit 63 ...") to include a language
identifier such as "text" or "txt" so it becomes "```text", leaving the block
contents unchanged.

In `@sql/spock--6.0.0.sql`:
- Around line 405-411: The view spock.sequence_info currently uses
sk.seqoid::regclass (in the CREATE VIEW spock.sequence_info SELECT block), which
will ERROR on stale OIDs after logical restore; modify the view to LEFT JOIN
pg_class (join pg_class c ON c.oid = sk.seqoid) and use c.relname (or construct
schema-qualified name from pg_namespace + relname) to present sequence_name
while marking or filtering rows where c.oid IS NULL as invalid/stale (e.g., set
sequence_name to NULL or 'INVALID_OID'); keep the existing
spock.sequence_hook_available() usage for hook_status and document or leave a
note to run spock.convert_all_sequences() after pg_restore if users prefer to
refresh OIDs.

In `@src/spock_executor.c`:
- Around line 239-259: The inner block in the sequence-drop handling shadows
outer variables save_userid and save_sec_context; remove the inner declarations
so the block reuses the outer-scope save_userid and save_sec_context variables
(keep the calls to GetUserIdAndSecContext, SetUserIdAndSecContext and the
spock_seqam_drop_sequence_record(objectId) call intact) to avoid variable
shadowing and improve readability around the RELKIND_SEQUENCE check and sequence
cleanup logic.
- Around line 295-316: Remove the inner declarations that shadow the outer saved
user/context variables and reuse the existing outer-scope variables instead: in
the OAT_POST_ALTER branch drop the lines declaring "Oid saved_userid; int
saved_sec_context;" and call GetUserIdAndSecContext, SetUserIdAndSecContext and
the spock_seqam_relocate_sequence_record(objectId) using the previously declared
outer variables (e.g., save_userid/save_sec_context or the existing saved_*
identifiers) so you don't shadow the outer scope and keep naming consistent.

In `@src/spock_seqam.c`:
- Around line 841-846: Header comment for validate_sequence_target is
misleading: it says SPOCK_SEQAM_SNOWFLAKE refuses seqmax < 1 << 22 but the code
actually requires NO MAXVALUE (maxv < PG_INT64_MAX). Update the header to match
the implementation by replacing the "seqmax < 1 << 22" clause with a statement
that snowflake mode requires no MAXVALUE (i.e., maxv == PG_INT64_MAX or
equivalent), and mention the actual check symbol names SPOCK_SEQAM_SNOWFLAKE,
validate_sequence_target, maxv, PG_INT64_MAX so readers can correlate comment to
the code paths.

In `@tests/regress/sql/seqam_snowflake.sql`:
- Around line 185-188: Replace the raw catalog deletion with the supported
dispatcher-invalidation calls: instead of "DELETE FROM spock.sequence_kind"
iterate over the sequence entries (e.g., local_seq, other_seq, sf_seq) and call
spock.alter_sequence_set_kind(sequence_regclass, 'local') for each to trigger
relcache invalidation; alternatively rely on the existing DROP SEQUENCE
statements to cascade the cleanup if the executor hook removes the
spock.sequence_kind rows. Ensure you use spock.alter_sequence_set_kind() rather
than direct manipulation of spock.sequence_kind to preserve the invalidation
contract.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dddbeff7-0e27-4b17-9054-4fe02332381c

📥 Commits

Reviewing files that changed from the base of the PR and between 3886058 and f2ddb02.

⛔ Files ignored due to path filters (1)
  • tests/regress/expected/init.out is excluded by !**/*.out
📒 Files selected for processing (21)
  • docs/distributed_sequences.md
  • include/spock_node.h
  • include/spock_seqam.h
  • mkdocs.yml
  • patches/15/pg15-050-nextval-hook.diff
  • patches/16/pg16-050-nextval-hook.diff
  • patches/17/pg17-050-nextval-hook.diff
  • patches/18/pg18-050-nextval-hook.diff
  • sql/spock--6.0.0.sql
  • src/compat/18/spock_compat.h
  • src/spock.c
  • src/spock_apply.c
  • src/spock_executor.c
  • src/spock_node.c
  • src/spock_seqam.c
  • src/spock_seqam_snowflake.c
  • src/spock_sequences.c
  • tests/regress/sql/init.sql
  • tests/regress/sql/seqam_snowflake.sql
  • tests/tap/schedule
  • tests/tap/t/050_seqam_snowflake.pl
💤 Files with no reviewable changes (1)
  • src/compat/18/spock_compat.h

Comment thread docs/distributed_sequences.md Outdated
Comment thread docs/distributed_sequences.md Outdated
Comment thread sql/spock--6.0.0.sql Outdated
Comment thread src/spock_seqam.c
Comment thread tests/regress/sql/seqam_snowflake.sql Outdated
Comment thread tests/tap/t/050_seqam_snowflake.pl Outdated
Comment thread tests/tap/t/050_seqam_snowflake.pl Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 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/distributed_sequences.md`:
- Around line 17-19: The document currently conflicts about whether
spock.sequence_kind is replicated via user_catalog_table or is node-local via
alter_sequence_set_kind(); choose one consistent semantics (either: 1)
sequence_kind is replicated cluster-wide via user_catalog_table, or 2)
sequence_kind is node-local and alter_sequence_set_kind() does not replicate)
and update all mentions to that behavior (including the paragraphs referencing
spock.sequence_kind, the description of user_catalog_table, the docs around
alter_sequence_set_kind(), and the operator guidance/warnings) so every section
(the earlier statement, the alter_sequence_set_kind() discussion, and the later
notes) present the same behavior and implications for cluster-wide uniqueness
and upgrade procedures. Ensure the chosen behavior also updates any examples,
caveats, and recommended operator steps to prevent accidental per-node
conversions.

In `@sql/spock--6.0.0.sql`:
- Around line 324-330: The runtime is using cached seqoid values that may be
stale after logical restore, causing wrong-row resolution/deletes; update the
runtime surface (the OAT_POST_ALTER hook and any view that references seqoid,
and functions like spock.alter_sequence_set_kind, spock.convert_all_sequences,
spock.sequence_info, and the spock.sequence_kind table usage) to never trust
stored seqoid alone: either resolve sequences by (nspname, relname) first and
refresh seqoid before performing any operations, or validate seqoid by
attempting safe resolution (e.g. check pg_class entry for the tuple and replace
stale seqoid with the current regclass OID) and only proceed when refreshed;
ensure any view/join uses (nspname, relname) as the authoritative key and update
stored seqoid values lazily when accessed.

In `@src/spock_executor.c`:
- Around line 253-258: Wrap the temporary elevation to BOOTSTRAP_SUPERUSERID
around the SeqAM calls with a PG_TRY / PG_FINALLY pair so the original
credentials are restored even if an ERROR is thrown: call
GetUserIdAndSecContext(&save_userid, &save_sec_context); then
SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, save_sec_context |
SECURITY_LOCAL_USERID_CHANGE); invoke spock_seqam_drop_sequence_record(objectId)
(and apply the same pattern for the OAT_POST_ALTER relocation block), and in
PG_FINALLY call SetUserIdAndSecContext(save_userid, save_sec_context); end with
PG_END_TRY(); ensure you keep the same local variables (save_userid,
save_sec_context) and the BOOTSTRAP_SUPERUSERID/SECURITY_LOCAL_USERID_CHANGE
flags so the restoration always runs on error or normal return.

In `@src/spock_node.c`:
- Around line 168-181: The helper spock_node_id_for_name currently returns any
16-bit Oid (possibly 0/InvalidOid) and doesn't ensure uniqueness in the
Snowflake domain; update logic so spock_node_id_for_name never returns
InvalidOid (reject or rehash until non-zero) and, where nodes are inserted (the
code path that uses node->id), validate that (node->id &
SPOCK_SNOWFLAKE_MAX_NODE_ID) is unique before writing the catalog row; if the
masked value collides, return an error to caller and refuse insert. Ensure you
reference SPOCK_SNOWFLAKE_NODE_BITS / SPOCK_SNOWFLAKE_MAX_NODE_ID for masking
and treat InvalidOid specially.

In `@src/spock_seqam.c`:
- Around line 547-556: When repopulating SeqAmCache you must revalidate
non-local managed kinds so we don't cache an incompatible sequence; after
getting kind from seqam_catalog_lookup (and after seqam_init_cache if needed)
add a call to validate_sequence_target(seqoid) for non-default kinds and, if
that validation fails, treat the sequence as unmanaged (e.g. set kind =
spock_seqam_default_kind) before creating the SeqAmCacheEntry; this prevents an
invalid managed kind from being cached and later passed to
spock_seqam_snowflake_nextval().
- Around line 564-568: Change the unsupported-state Assert checks into runtime
guards: replace the Assert(...) calls for RecoveryInProgress(),
creating_extension, IsBinaryUpgrade and the kind range check (kind >
SPOCK_SEQAM_LOCAL && kind < SPOCK_SEQAM_NMETHODS) with an if that detects any of
these conditions at runtime and raises a PostgreSQL error (ereport/elog)
returning ERRCODE_FEATURE_NOT_SUPPORTED and a clear errmsg explaining that the
requested spock.default_sequence_kind (e.g. "snowflake") is unsupported during
recovery, extension creation or binary-upgrade; reference the symbols
RecoveryInProgress, creating_extension, IsBinaryUpgrade, kind,
SPOCK_SEQAM_LOCAL, SPOCK_SEQAM_NMETHODS and mention spock.default_sequence_kind
in the message so callers get a descriptive runtime error instead of an
assertion in production.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 726d1de6-af5f-474e-8354-d13abe1c24a8

📥 Commits

Reviewing files that changed from the base of the PR and between 0c862aa and 9d8735c.

⛔ Files ignored due to path filters (1)
  • tests/regress/expected/init.out is excluded by !**/*.out
📒 Files selected for processing (22)
  • docs/distributed_sequences.md
  • include/spock_node.h
  • include/spock_seqam.h
  • mkdocs.yml
  • patches/15/pg15-050-nextval-hook.diff
  • patches/16/pg16-050-nextval-hook.diff
  • patches/17/pg17-050-nextval-hook.diff
  • patches/18/pg18-050-nextval-hook.diff
  • sql/spock--6.0.0.sql
  • src/compat/15/spock_compat.h
  • src/compat/16/spock_compat.h
  • src/compat/17/spock_compat.h
  • src/compat/18/spock_compat.h
  • src/spock.c
  • src/spock_apply.c
  • src/spock_executor.c
  • src/spock_node.c
  • src/spock_seqam.c
  • src/spock_seqam_snowflake.c
  • src/spock_sequences.c
  • tests/regress/sql/init.sql
  • tests/tap/schedule
💤 Files with no reviewable changes (4)
  • src/compat/18/spock_compat.h
  • src/compat/17/spock_compat.h
  • src/compat/15/spock_compat.h
  • src/compat/16/spock_compat.h
✅ Files skipped from review due to trivial changes (1)
  • mkdocs.yml

Comment on lines +17 to +19
`spock.sequence_kind` and replicated to all nodes via Spock's
`user_catalog_table` machinery, so every node converges on the same view
of which sequences are managed.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Conflicting replication semantics for spock.sequence_kind need reconciliation.

Line 17 says assignments replicate via user_catalog_table, but Line 87 through Line 94 says alter_sequence_set_kind() is node-local and does not replicate. The same conflict reappears at Line 239 through Line 243 and Line 260. Please align these sections to one behavior, otherwise operators may convert only one node and lose cluster-wide uniqueness guarantees.

🤖 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 `@docs/distributed_sequences.md` around lines 17 - 19, The document currently
conflicts about whether spock.sequence_kind is replicated via user_catalog_table
or is node-local via alter_sequence_set_kind(); choose one consistent semantics
(either: 1) sequence_kind is replicated cluster-wide via user_catalog_table, or
2) sequence_kind is node-local and alter_sequence_set_kind() does not replicate)
and update all mentions to that behavior (including the paragraphs referencing
spock.sequence_kind, the description of user_catalog_table, the docs around
alter_sequence_set_kind(), and the operator guidance/warnings) so every section
(the earlier statement, the alter_sequence_set_kind() discussion, and the later
notes) present the same behavior and implications for cluster-wide uniqueness
and upgrade procedures. Ensure the chosen behavior also updates any examples,
caveats, and recommended operator steps to prevent accidental per-node
conversions.

Comment thread sql/spock--6.0.0.sql
Comment thread src/spock_executor.c
Comment on lines +253 to +258
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
save_sec_context |
SECURITY_LOCAL_USERID_CHANGE);
spock_seqam_drop_sequence_record(objectId);
SetUserIdAndSecContext(save_userid, save_sec_context);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/spock_executor.c"

# Show the referenced regions with line numbers
echo "=== Region around 240-330 ==="
nl -ba "$FILE" | sed -n '220,340p'

# Also locate the helper calls to confirm exact surrounding context
echo "=== Find SeqAM helper calls ==="
rg -n "spock_seqam_(drop_sequence_record|relocate_sequence_record)_record|SetUserIdAndSecContext|BOOTSTRAP_SUPERUSERID|PG_TRY|PG_FINALLY|PG_END_TRY" "$FILE"

Repository: pgEdge/spock

Length of output: 147


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/spock_executor.c"

echo "=== Region around 240-330 ==="
# Use awk to add line numbers (nl may be unavailable)
awk 'NR>=220 && NR<=340 {printf "%6d\t%s\n", NR, $0}' "$FILE"

echo "=== Find SeqAM helper calls + privilege switches + PG_TRY/FINALLY usage ==="
rg -n "spock_seqam_(drop_sequence_record|relocate_sequence_record)_record|SetUserIdAndSecContext|BOOTSTRAP_SUPERUSERID|PG_TRY|PG_FINALLY|PG_END_TRY" "$FILE"

Repository: pgEdge/spock

Length of output: 5226


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "PG_TRY|PG_FINALLY|PG_END_TRY" src/spock_executor.c || true

# Find definitions/implementations of the SeqAM helper functions
rg -n "spock_seqam_(drop_sequence_record|relocate_sequence_record)_record" -S src || true

# Show candidate implementation locations (function headers)
rg -n "void\s+spock_seqam_(drop_sequence_record|relocate_sequence_record)_record|spock_seqam_(drop_sequence_record|relocate_sequence_record)_record\s*\(" -S src || true

Repository: pgEdge/spock

Length of output: 38


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Locate SeqAM helper declarations/definitions across repo ==="
rg -n "spock_seqam_(drop_sequence_record|relocate_sequence_record)_record" -S . || true

echo "=== Where PG_TRY/PG_FINALLY is used around privilege switching in this repo ==="
rg -n "GetUserIdAndSecContext\\(|SetUserIdAndSecContext\\(|BOOTSTRAP_SUPERUSERID" -S src | head -n 200

echo "=== Check if SetUserIdAndSecContext/ GetUserIdAndSecContext wrappers already handle ERROR cleanup ==="
rg -n "GetUserIdAndSecContext\\b|SetUserIdAndSecContext\\b" -S . || true

echo "=== Search for PG_FINALLY/PG_TRY usage patterns in executor hooks ==="
rg -n "PG_TRY\\(|PG_FINALLY\\(|PG_END_TRY\\(" -S src | head -n 200

Repository: pgEdge/spock

Length of output: 4911


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and show implementations in src/spock_seqam.c
FILE="src/spock_seqam.c"
echo "=== Drop sequence record implementation (if present) ==="
rg -n "spock_seqam_drop_sequence_record\\s*\\(" "$FILE" || true
# Print a window around the match (if any)
line=$(rg -n "spock_seqam_drop_sequence_record\\s*\\(" "$FILE" | head -n1 | cut -d: -f1 || true)
if [[ -n "${line:-}" ]]; then
  start=$((line-30)); end=$((line+120));
  awk -v s=$start -v e=$end 'NR>=s && NR<=e {printf "%6d\t%s\n", NR, $0}' "$FILE"
fi

echo "=== Relocate sequence record implementation (if present) ==="
rg -n "spock_seqam_relocate_sequence_record\\s*\\(" "$FILE" || true
line=$(rg -n "spock_seqam_relocate_sequence_record\\s*\\(" "$FILE" | head -n1 | cut -d: -f1 || true)
if [[ -n "${line:-}" ]]; then
  start=$((line-30)); end=$((line+120));
  awk -v s=$start -v e=$end 'NR>=s && NR<=e {printf "%6d\t%s\n", NR, $0}' "$FILE"
fi

# Inspect security-context helper definitions
echo "=== GetUserIdAndSecContext / SetUserIdAndSecContext implementations ==="
rg -n "GetUserIdAndSecContext\\s*\\(|SetUserIdAndSecContext\\s*\\(" src/spock_common.c src -S | head -n 50

echo "=== Show spock_common.c around those helpers ==="
FILE="src/spock_common.c"
# Show early part of file where they likely live
awk 'NR>=1 && NR<=140 {printf "%6d\t%s\n", NR, $0}' "$FILE"

Repository: pgEdge/spock

Length of output: 19045


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/spock_seqam.c"

echo "=== ereport(ERROR) occurrences in file (to see if helpers can throw) ==="
rg -n "ereport\\(ERROR" "$FILE" || true

echo "=== Show seqam_resolve_seq_name implementation (definition + any ERROR) ==="
rg -n "seqam_resolve_seq_name\\s*\\(" "$FILE"
line=$(rg -n "seqam_resolve_seq_name\\s*\\(" "$FILE" | head -n1 | cut -d: -f1)
start=$((line-60)); end=$((line+120));
awk -v s=$start -v e=$end 'NR>=s && NR<=e {printf "%6d\t%s\n", NR, $0}' "$FILE"

echo "=== Scan inside drop/relocate functions for ereport/ERROR-like calls ==="
# approximate ranges from earlier output: drop around 653-695, relocate around 715-807
awk 'NR>=640 && NR<=710 {print}' "$FILE" | rg -n "ereport\\(|elog\\(|ERROR" || true
awk 'NR>=700 && NR<=830 {print}' "$FILE" | rg -n "ereport\\(|elog\\(|ERROR" || true

Repository: pgEdge/spock

Length of output: 6811


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where GetUserIdAndSecContext / SetUserIdAndSecContext are defined
rg -n "GetUserIdAndSecContext\\s*\\(" -S src | head -n 50
rg -n "SetUserIdAndSecContext\\s*\\(" -S src | head -n 50

# Specifically look for definitions (not just calls) by matching with return type patterns
rg -n "void\\s+GetUserIdAndSecContext\\b|static\\s+void\\s+GetUserIdAndSecContext\\b|GetUserIdAndSecContext\\b\\s*\\{" -S src || true
rg -n "void\\s+SetUserIdAndSecContext\\b|static\\s+void\\s+SetUserIdAndSecContext\\b|SetUserIdAndSecContext\\b\\s*\\{" -S src || true

# Confirm whether any ereport/elog appears within the drop/relocate functions ranges
FILE="src/spock_seqam.c"
echo "=== ereport/elog inside drop function range (640-710) ==="
awk 'NR>=640 && NR<=710 {print}' "$FILE" | rg -n "ereport\\(|elog\\(|ERROR" || true
echo "=== ereport/elog inside relocate function range (700-820) ==="
awk 'NR>=700 && NR<=820 {print}' "$FILE" | rg -n "ereport\\(|elog\\(|ERROR" || true

Repository: pgEdge/spock

Length of output: 1687


Harden the BOOTSTRAP_SUPERUSERID elevation with PG_TRY/PG_FINALLY (both sequence blocks)

The two sequences blocks in src/spock_executor.c (drop cleanup and OAT_POST_ALTER relocation) restore the caller’s userid only on the normal return path. The SeqAM helpers don’t explicitly ereport(ERROR) in their bodies, but catalog/syscache operations they call could still throw an ERROR; wrapping those elevations in PG_TRY/PG_FINALLY would guarantee SetUserIdAndSecContext(saved_*...) runs even on ERROR.

🤖 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 `@src/spock_executor.c` around lines 253 - 258, Wrap the temporary elevation to
BOOTSTRAP_SUPERUSERID around the SeqAM calls with a PG_TRY / PG_FINALLY pair so
the original credentials are restored even if an ERROR is thrown: call
GetUserIdAndSecContext(&save_userid, &save_sec_context); then
SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, save_sec_context |
SECURITY_LOCAL_USERID_CHANGE); invoke spock_seqam_drop_sequence_record(objectId)
(and apply the same pattern for the OAT_POST_ALTER relocation block), and in
PG_FINALLY call SetUserIdAndSecContext(save_userid, save_sec_context); end with
PG_END_TRY(); ensure you keep the same local variables (save_userid,
save_sec_context) and the BOOTSTRAP_SUPERUSERID/SECURITY_LOCAL_USERID_CHANGE
flags so the restoration always runs on error or normal return.

Comment thread src/spock_node.c
Comment on lines +168 to +181
/*
* Derive a Spock node id from a node name. Truncated to 16 bits so the
* value fits cleanly inside an Oid column and leaves room for downstream
* uses (snowflake sequences mask off the low 10 bits as their node_id).
* Same input -> same output across nodes; callers that need cluster-wide
* uniqueness must verify by inspecting peers.
*/
Oid
spock_node_id_for_name(const char *name)
{
return (Oid)
(DatumGetUInt32(hash_any((const unsigned char *) name,
strlen(name))) & 0xffff);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate the Snowflake node-id domain before storing the node.

This path accepts any 16-bit Oid, but Snowflake only encodes the low 10 bits (SPOCK_SNOWFLAKE_NODE_BITS). Two different node IDs that differ outside those 10 bits can both be stored here and later emit identical Snowflake values on different nodes. hash_any(...) & 0xffff can also produce 0, so this helper can persist InvalidOid as a generated node id.

Please reject InvalidOid and enforce uniqueness on the effective Snowflake node id (node->id & SPOCK_SNOWFLAKE_MAX_NODE_ID) before inserting the catalog row.

Also applies to: 200-202

🤖 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 `@src/spock_node.c` around lines 168 - 181, The helper spock_node_id_for_name
currently returns any 16-bit Oid (possibly 0/InvalidOid) and doesn't ensure
uniqueness in the Snowflake domain; update logic so spock_node_id_for_name never
returns InvalidOid (reject or rehash until non-zero) and, where nodes are
inserted (the code path that uses node->id), validate that (node->id &
SPOCK_SNOWFLAKE_MAX_NODE_ID) is unique before writing the catalog row; if the
masked value collides, return an error to caller and refuse insert. Ensure you
reference SPOCK_SNOWFLAKE_NODE_BITS / SPOCK_SNOWFLAKE_MAX_NODE_ID for masking
and treat InvalidOid specially.

Comment thread src/spock_seqam.c
Comment on lines +547 to +556
if (!seqam_catalog_lookup(seqoid, &kind))
kind = spock_seqam_default_kind;

/* Re-create if a callback nuked the cache during the lookup. */
if (SeqAmCache == NULL)
seqam_init_cache();

entry = (SeqAmCacheEntry *) hash_search(SeqAmCache, &seqoid,
HASH_ENTER, &found);
entry->kind = kind;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Revalidate non-local kinds when repopulating the cache.

validate_sequence_target() only runs in the SQL admin entry points. After a relcache invalidation, or when spock.default_sequence_kind = snowflake, this path caches a managed kind without rechecking AS bigint / NO MAXVALUE / CACHE 1 / positive INCREMENT. That lets an incompatible or later-altered sequence fall through into spock_seqam_snowflake_nextval() with semantics it cannot honor.

Suggested fix
-		if (!seqam_catalog_lookup(seqoid, &kind))
-			kind = spock_seqam_default_kind;
+		if (!seqam_catalog_lookup(seqoid, &kind))
+			kind = spock_seqam_default_kind;
+
+		if (kind != SPOCK_SEQAM_LOCAL)
+			validate_sequence_target(seqoid, kind);
🤖 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 `@src/spock_seqam.c` around lines 547 - 556, When repopulating SeqAmCache you
must revalidate non-local managed kinds so we don't cache an incompatible
sequence; after getting kind from seqam_catalog_lookup (and after
seqam_init_cache if needed) add a call to validate_sequence_target(seqoid) for
non-default kinds and, if that validation fails, treat the sequence as unmanaged
(e.g. set kind = spock_seqam_default_kind) before creating the SeqAmCacheEntry;
this prevents an invalid managed kind from being cached and later passed to
spock_seqam_snowflake_nextval().

Comment thread src/spock_seqam.c
Comment on lines +564 to +568
/* Managed dispatch is unsupported in these states. */
Assert(!RecoveryInProgress());
Assert(!creating_extension);
Assert(!IsBinaryUpgrade);
Assert(kind > SPOCK_SEQAM_LOCAL && kind < SPOCK_SEQAM_NMETHODS);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Turn these unsupported-state asserts into runtime guards.

In production builds, these Asserts disappear. A cluster-wide spock.default_sequence_kind = snowflake can still route nextval() calls made during extension creation or binary upgrade into a path the code explicitly says is unsupported.

Suggested fix
-	Assert(!RecoveryInProgress());
-	Assert(!creating_extension);
-	Assert(!IsBinaryUpgrade);
+	if (RecoveryInProgress() || creating_extension || IsBinaryUpgrade)
+		return sequence_am_local_nextval(rel, incby, maxv, minv,
+										 cache, cycle, last);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* Managed dispatch is unsupported in these states. */
Assert(!RecoveryInProgress());
Assert(!creating_extension);
Assert(!IsBinaryUpgrade);
Assert(kind > SPOCK_SEQAM_LOCAL && kind < SPOCK_SEQAM_NMETHODS);
/* Managed dispatch is unsupported in these states. */
if (RecoveryInProgress() || creating_extension || IsBinaryUpgrade)
return sequence_am_local_nextval(rel, incby, maxv, minv,
cache, cycle, last);
Assert(kind > SPOCK_SEQAM_LOCAL && kind < SPOCK_SEQAM_NMETHODS);
🤖 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 `@src/spock_seqam.c` around lines 564 - 568, Change the unsupported-state
Assert checks into runtime guards: replace the Assert(...) calls for
RecoveryInProgress(), creating_extension, IsBinaryUpgrade and the kind range
check (kind > SPOCK_SEQAM_LOCAL && kind < SPOCK_SEQAM_NMETHODS) with an if that
detects any of these conditions at runtime and raises a PostgreSQL error
(ereport/elog) returning ERRCODE_FEATURE_NOT_SUPPORTED and a clear errmsg
explaining that the requested spock.default_sequence_kind (e.g. "snowflake") is
unsupported during recovery, extension creation or binary-upgrade; reference the
symbols RecoveryInProgress, creating_extension, IsBinaryUpgrade, kind,
SPOCK_SEQAM_LOCAL, SPOCK_SEQAM_NMETHODS and mention spock.default_sequence_kind
in the message so callers get a descriptive runtime error instead of an
assertion in production.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
docs/distributed_sequences.md (2)

282-283: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix duplicate ordered-list numbering.

The list currently goes 1, 2, 3, 3 at Line 282. The final item should be 4..

🤖 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 `@docs/distributed_sequences.md` around lines 282 - 283, Fix the duplicate
ordered-list numbering by changing the final list item label from "3." to "4."
so the sequence reads 1, 2, 3, 4; locate the ordered list containing the lines
mentioning "nextval()" and "Snowflake" and update the numeric marker for that
last item to "4." to correct the Markdown numbering.

17-19: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Conflicting replication semantics for spock.sequence_kind are still present.

Line 17 says assignments replicate via user_catalog_table, but Line 80-87 says changes are node-local and not replicated; Line 243-244 and Line 280-281 repeat the “replicates” wording. Please pick one behavior and align all sections so operator guidance is unambiguous.

Also applies to: 222-224, 243-244, 280-281

🤖 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 `@docs/distributed_sequences.md` around lines 17 - 19, The document currently
contains conflicting statements about whether spock.sequence_kind assignments
replicate via Spock's user_catalog_table or are node-local; pick one definitive
behavior and make the wording consistent across all places that mention sequence
replication (every paragraph referencing spock.sequence_kind,
user_catalog_table, or saying "replicates" vs "node-local" — e.g., the sections
that describe assignment/replication semantics and the paragraphs that describe
changes being node-local). Update those sections to use the chosen wording,
adjust any operator guidance to match that behavior (including any instructions
for making changes cluster-wide vs per-node), and ensure the terms "replicates",
"replicated", and "node-local" are used unambiguously and consistently wherever
spock.sequence_kind is discussed.
🧹 Nitpick comments (2)
src/spock_executor.c (1)

250-251: 💤 Low value

Variable shadowing: inner save_userid/save_sec_context hide outer declarations.

Lines 205-206 already declare save_userid and save_sec_context at function scope (used later in the OAT_DROP path at lines 274-293). The inner declarations here shadow those, which compiles but is confusing and could lead to bugs if someone later tries to use the outer variables expecting them to be set.

Consider removing the inner declarations and reusing the outer ones, or rename these to match the OAT_POST_ALTER block's naming (saved_userid, saved_sec_context).

♻️ Suggested fix
 			if (get_rel_relkind(objectId) == RELKIND_SEQUENCE &&
 				!dropping_spock_obj)
 			{
-				Oid	save_userid;
-				int	save_sec_context;
-
 				GetUserIdAndSecContext(&save_userid, &save_sec_context);
 				SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
 									   save_sec_context |
🤖 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 `@src/spock_executor.c` around lines 250 - 251, The inner declarations "Oid
save_userid; int save_sec_context;" inside the OAT_DROP handling shadow the
function-scope variables with the same names declared earlier; remove these
inner declarations and reuse the outer save_userid and save_sec_context, or
rename the inner ones to match the OAT_POST_ALTER pattern (saved_userid,
saved_sec_context) so there is no shadowing; update any assignments/uses in the
OAT_DROP block (and corresponding restore logic later) to reference the chosen
uniquely named variables (save_userid/save_sec_context or
saved_userid/saved_sec_context) consistently.
src/spock_seqam_snowflake.c (1)

239-262: 💤 Low value

Confirm the design decision to silently ignore maxv, minv, cache, cycle.

The function casts these to (void) and never validates against them. For users this means ALTER SEQUENCE ... MAXVALUE / MINVALUE / CYCLE / CACHE on a snowflake-managed sequence has no effect on the emitted values — snowflakes can and will exceed the configured MAXVALUE without error. If this is the intended contract (which the file header and PR summary suggest it is), it's worth at least an elog(DEBUG2, ...) or a one-line comment near the cast to make the silence explicit, or alternatively a guard in alter_sequence_set_kind() so that switching kind rejects non-defaults to fail loudly.

🤖 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 `@src/spock_seqam_snowflake.c` around lines 239 - 262,
spock_seqam_snowflake_nextval currently ignores maxv, minv, cache, and cycle by
casting them to (void) which can surprise users; either make the silence
explicit by adding an elog(DEBUG2, "...") in spock_seqam_snowflake_nextval near
the (void) casts (mentioning that snowflake-managed sequences ignore
MAXVALUE/MINVALUE/CACHE/CYCLE), or instead add a guard in
alter_sequence_set_kind() to reject/non-accept non-default
MAXVALUE/MINVALUE/CACHE/CYCLE when switching to the snowflake kind (e.g.,
validate and elog(ERROR) or return an error), and document the behavior in a
one-line comment adjacent to the (void) casts; reference
spock_seqam_snowflake_nextval and alter_sequence_set_kind when implementing the
change.
🤖 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/distributed_sequences.md`:
- Around line 140-146: The fenced code block showing the bit layout (the block
starting with the three backticks and lines like "bit 63 : reserved" and "bits
62..22 : 41-bit timestamp") is missing a language identifier and triggers
markdownlint MD040; update the opening fence from ``` to ```text (or another
appropriate language) so the block is explicitly marked as plain text, leaving
the interior content (bit 63, bits 62..22, bits 21..12, bits 11..0) unchanged.

In `@src/spock_seqam_snowflake.c`:
- Around line 318-361: The candidate_ctr handling fails to guard negative incby
values; update the overflow check that currently tests "candidate_ctr >
SPOCK_SNOWFLAKE_COUNTER_MASK" to also detect "candidate_ctr < 0" and treat it as
an error path: when candidate_ctr is out of [0, SPOCK_SNOWFLAKE_COUNTER_MASK]
(i.e., < 0 or > COUNTER_MASK) release the buffer (UnlockReleaseBuffer(buf)) and
raise an ereport(ERROR) similar to the existing branch so we never cast a
negative candidate_ctr into new_ctr; use the same error code/message pattern and
reference the same symbols (candidate_ctr, incby, new_ctr,
SPOCK_SNOWFLAKE_COUNTER_MASK, new_ts, UnlockReleaseBuffer, ereport) so the
negative-INCREMENT case is rejected rather than producing a negative new_ctr
that corrupts the encoded ID.
- Around line 147-157: sf_unpack is extracting the timestamp from the wrong bit
range because SF_STATE_TS_SHIFT is defined as SPOCK_SNOWFLAKE_COUNTER_BITS (12)
instead of the full SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT (node_bits + counter_bits =
22); update the mask/shift definitions used by sf_unpack so the timestamp is
decoded with SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT (and SF_STATE_TS_MASK is computed
from that shift), or directly use SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT in sf_unpack
to extract *ts from packed; keep SF_STATE_CTR_MASK extraction for *ctr
unchanged.

---

Duplicate comments:
In `@docs/distributed_sequences.md`:
- Around line 282-283: Fix the duplicate ordered-list numbering by changing the
final list item label from "3." to "4." so the sequence reads 1, 2, 3, 4; locate
the ordered list containing the lines mentioning "nextval()" and "Snowflake" and
update the numeric marker for that last item to "4." to correct the Markdown
numbering.
- Around line 17-19: The document currently contains conflicting statements
about whether spock.sequence_kind assignments replicate via Spock's
user_catalog_table or are node-local; pick one definitive behavior and make the
wording consistent across all places that mention sequence replication (every
paragraph referencing spock.sequence_kind, user_catalog_table, or saying
"replicates" vs "node-local" — e.g., the sections that describe
assignment/replication semantics and the paragraphs that describe changes being
node-local). Update those sections to use the chosen wording, adjust any
operator guidance to match that behavior (including any instructions for making
changes cluster-wide vs per-node), and ensure the terms "replicates",
"replicated", and "node-local" are used unambiguously and consistently wherever
spock.sequence_kind is discussed.

---

Nitpick comments:
In `@src/spock_executor.c`:
- Around line 250-251: The inner declarations "Oid save_userid; int
save_sec_context;" inside the OAT_DROP handling shadow the function-scope
variables with the same names declared earlier; remove these inner declarations
and reuse the outer save_userid and save_sec_context, or rename the inner ones
to match the OAT_POST_ALTER pattern (saved_userid, saved_sec_context) so there
is no shadowing; update any assignments/uses in the OAT_DROP block (and
corresponding restore logic later) to reference the chosen uniquely named
variables (save_userid/save_sec_context or saved_userid/saved_sec_context)
consistently.

In `@src/spock_seqam_snowflake.c`:
- Around line 239-262: spock_seqam_snowflake_nextval currently ignores maxv,
minv, cache, and cycle by casting them to (void) which can surprise users;
either make the silence explicit by adding an elog(DEBUG2, "...") in
spock_seqam_snowflake_nextval near the (void) casts (mentioning that
snowflake-managed sequences ignore MAXVALUE/MINVALUE/CACHE/CYCLE), or instead
add a guard in alter_sequence_set_kind() to reject/non-accept non-default
MAXVALUE/MINVALUE/CACHE/CYCLE when switching to the snowflake kind (e.g.,
validate and elog(ERROR) or return an error), and document the behavior in a
one-line comment adjacent to the (void) casts; reference
spock_seqam_snowflake_nextval and alter_sequence_set_kind when implementing the
change.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cd1f675b-b936-4bce-9903-afe3e819912c

📥 Commits

Reviewing files that changed from the base of the PR and between 9d8735c and 2013009.

⛔ Files ignored due to path filters (1)
  • tests/regress/expected/init.out is excluded by !**/*.out
📒 Files selected for processing (22)
  • docs/distributed_sequences.md
  • include/spock_node.h
  • include/spock_seqam.h
  • mkdocs.yml
  • patches/15/pg15-050-nextval-hook.diff
  • patches/16/pg16-050-nextval-hook.diff
  • patches/17/pg17-050-nextval-hook.diff
  • patches/18/pg18-050-nextval-hook.diff
  • sql/spock--6.0.0.sql
  • src/compat/15/spock_compat.h
  • src/compat/16/spock_compat.h
  • src/compat/17/spock_compat.h
  • src/compat/18/spock_compat.h
  • src/spock.c
  • src/spock_apply.c
  • src/spock_executor.c
  • src/spock_node.c
  • src/spock_seqam.c
  • src/spock_seqam_snowflake.c
  • src/spock_sequences.c
  • tests/regress/sql/init.sql
  • tests/tap/schedule
💤 Files with no reviewable changes (4)
  • src/compat/16/spock_compat.h
  • src/compat/15/spock_compat.h
  • src/compat/18/spock_compat.h
  • src/compat/17/spock_compat.h
✅ Files skipped from review due to trivial changes (1)
  • mkdocs.yml

Comment on lines +140 to +146
```
bit 63 : reserved, always 0 (sign bit, keeps values non-negative)
bits 62..22 : 41-bit timestamp in milliseconds since the Spock epoch
(2023-01-01 00:00:00 UTC; matches the standalone `snowflake` extension)
bits 21..12 : 10-bit node id (0..1023)
bits 11..0 : 12-bit counter (0..4095)
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language to the fenced code block.

The fence at Line 140 is missing a language identifier and trips markdownlint MD040. Use something like ```text for the bit layout block.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 140-140: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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 `@docs/distributed_sequences.md` around lines 140 - 146, The fenced code block
showing the bit layout (the block starting with the three backticks and lines
like "bit 63 : reserved" and "bits 62..22 : 41-bit timestamp") is missing a
language identifier and triggers markdownlint MD040; update the opening fence
from ``` to ```text (or another appropriate language) so the block is explicitly
marked as plain text, leaving the interior content (bit 63, bits 62..22, bits
21..12, bits 11..0) unchanged.

Comment on lines +147 to +157
#define SF_STATE_TS_SHIFT SPOCK_SNOWFLAKE_COUNTER_BITS
#define SF_STATE_CTR_MASK SPOCK_SNOWFLAKE_COUNTER_MASK
#define SF_STATE_TS_MASK (((UINT64CONST(1) << SPOCK_SNOWFLAKE_TIMESTAMP_BITS) - 1) \
<< SF_STATE_TS_SHIFT)

static inline void
sf_unpack(uint64 packed, int64 *ts, int32 *ctr)
{
*ctr = (int32) (packed & SF_STATE_CTR_MASK);
*ts = (int64) ((packed & SF_STATE_TS_MASK) >> SF_STATE_TS_SHIFT);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Dump the snowflake bit-layout constants in the header and any uses elsewhere.
fd -t f 'spock_seqam.h' | xargs -I{} sh -c 'echo "==== {} ===="; cat "{}"'
echo "---- uses of TIMESTAMP_SHIFT / NODE_SHIFT / COUNTER_BITS / NODE_MASK ----"
rg -nP '\bSPOCK_SNOWFLAKE_(TIMESTAMP_SHIFT|NODE_SHIFT|NODE_MASK|COUNTER_BITS|TIMESTAMP_BITS|COUNTER_MASK|MAX_NODE_ID)\b' -C2

Repository: pgEdge/spock

Length of output: 12560


🏁 Script executed:

#!/bin/bash
# Inspect incby validation context in src/spock_seqam.c
sed -n '900,990p' src/spock_seqam.c

# Where sf_unpack / SF_STATE_TS_SHIFT are used
rg -n "SF_STATE_TS_SHIFT|sf_unpack\s*\(" -S src/spock_seqam_snowflake.c

Repository: pgEdge/spock

Length of output: 3732


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '120,190p' src/spock_seqam_snowflake.c
sed -n '260,330p' src/spock_seqam_snowflake.c
sed -n '360,420p' src/spock_seqam_snowflake.c
sed -n '430,520p' src/spock_seqam_snowflake.c

Repository: pgEdge/spock

Length of output: 8971


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "INCREMENT|increment" src/spock_seqam.c src/spock_executor.c src/spock_seqam_snowflake.c include/spock_seqam.h
rg -n "SPOCK_SNOWFLAKE_COUNTER_MASK" src/spock_seqam.c src/spock_seqam_snowflake.c src/spock_executor.c

Repository: pgEdge/spock

Length of output: 1387


Fix sf_unpack to decode the timestamp using SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT

include/spock_seqam.h defines SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT = (SPOCK_SNOWFLAKE_NODE_BITS + SPOCK_SNOWFLAKE_COUNTER_BITS) = 22 and SPOCK_SNOWFLAKE_NODE_SHIFT = 12. seq->last_value is written using new_ts << SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT, but cur_packed = seq->last_value & ~SPOCK_SNOWFLAKE_NODE_MASK only zeroes node bits (does not shift the timestamp). sf_unpack currently sets SF_STATE_TS_SHIFT to SPOCK_SNOWFLAKE_COUNTER_BITS (12), so it extracts the timestamp from the wrong bit range and drops the upper bits, corrupting cur_ts and the subsequent control/WAL decisions.

Diff
-#define SF_STATE_TS_SHIFT		SPOCK_SNOWFLAKE_COUNTER_BITS
+#define SF_STATE_TS_SHIFT		SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT
 `#define` SF_STATE_CTR_MASK		SPOCK_SNOWFLAKE_COUNTER_MASK
 `#define` SF_STATE_TS_MASK		(((UINT64CONST(1) << SPOCK_SNOWFLAKE_TIMESTAMP_BITS) - 1) \
 								 << SF_STATE_TS_SHIFT)

Negative INCREMENT handling is already rejected up-front (spock_seqam.c errors on incby <= 0), so that specific concern doesn’t apply here.

🤖 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 `@src/spock_seqam_snowflake.c` around lines 147 - 157, sf_unpack is extracting
the timestamp from the wrong bit range because SF_STATE_TS_SHIFT is defined as
SPOCK_SNOWFLAKE_COUNTER_BITS (12) instead of the full
SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT (node_bits + counter_bits = 22); update the
mask/shift definitions used by sf_unpack so the timestamp is decoded with
SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT (and SF_STATE_TS_MASK is computed from that
shift), or directly use SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT in sf_unpack to extract
*ts from packed; keep SF_STATE_CTR_MASK extraction for *ctr unchanged.

Comment on lines +318 to +361
* Do the arithmetic in int64 so a user who ALTERs INCREMENT to
* a large value after the kind was set (bypassing
* validate_sequence_target) cannot trigger int32 overflow here.
* validate_sequence_target enforces 1..COUNTER_MASK at SET KIND
* time; this is belt-and-braces.
*/
new_ts = cur_ts;
candidate_ctr = (int64) cur_ctr + incby;

if (candidate_ctr > (int64) SPOCK_SNOWFLAKE_COUNTER_MASK)
{
/*
* Counter exhausted in this millisecond. Advance the
* timestamp by one ms and reset the counter. We are now
* synthesising a future timestamp; the wall clock will
* catch up.
*/
new_ts = cur_ts + 1;
new_ctr = 0;

/*
* Sustained > 4M nextval()/s/sequence/node could push the
* synthesised timestamp arbitrarily far into the future and
* ultimately overflow the 41-bit field. Refuse the write
* here rather than silently wrapping.
*/
if (((uint64) new_ts) >> SPOCK_SNOWFLAKE_TIMESTAMP_BITS)
{
UnlockReleaseBuffer(buf);
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("snowflake timestamp field exhausted under saturation"),
errdetail("Per-millisecond counter exhaustion has pushed the "
"synthesised timestamp beyond the 41-bit field "
"(year ~2092). Reduce sustained nextval() throughput "
"or assign additional snowflake-managed sequences "
"to spread the load.")));
}
}
else
{
/* Safe: candidate_ctr is in [0, COUNTER_MASK], fits int32. */
new_ctr = (int32) candidate_ctr;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Belt-and-braces only guards the upper bound on candidate_ctr.

The comment explicitly anticipates a user ALTER SEQUENCE … INCREMENT BY <n> bypassing validate_sequence_target, but the check at line 327 only catches candidate_ctr > SPOCK_SNOWFLAKE_COUNTER_MASK. A negative incby (e.g., user altered INCREMENT to -1) produces a negative candidate_ctr, which then casts to a negative int32 new_ctr at line 360. At line 387 that new_ctr is widened with (uint64) new_ctr, sign-extending the value into the timestamp/node-id bit ranges of value and corrupting the encoded ID. Adding a candidate_ctr < 0 arm to the same check would close the gap.

🛡 Proposed fix
-		if (candidate_ctr > (int64) SPOCK_SNOWFLAKE_COUNTER_MASK)
+		if (candidate_ctr < 0 ||
+			candidate_ctr > (int64) SPOCK_SNOWFLAKE_COUNTER_MASK)
 		{
 			/*
-			 * Counter exhausted in this millisecond.  Advance the
-			 * timestamp by one ms and reset the counter.  We are now
-			 * synthesising a future timestamp; the wall clock will
-			 * catch up.
+			 * Counter exhausted in this millisecond (or `incby` is
+			 * non-positive after a stray ALTER SEQUENCE).  Advance
+			 * the timestamp by one ms and reset the counter.  We are
+			 * now synthesising a future timestamp; the wall clock
+			 * will catch up.
 			 */
🤖 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 `@src/spock_seqam_snowflake.c` around lines 318 - 361, The candidate_ctr
handling fails to guard negative incby values; update the overflow check that
currently tests "candidate_ctr > SPOCK_SNOWFLAKE_COUNTER_MASK" to also detect
"candidate_ctr < 0" and treat it as an error path: when candidate_ctr is out of
[0, SPOCK_SNOWFLAKE_COUNTER_MASK] (i.e., < 0 or > COUNTER_MASK) release the
buffer (UnlockReleaseBuffer(buf)) and raise an ereport(ERROR) similar to the
existing branch so we never cast a negative candidate_ctr into new_ctr; use the
same error code/message pattern and reference the same symbols (candidate_ctr,
incby, new_ctr, SPOCK_SNOWFLAKE_COUNTER_MASK, new_ts, UnlockReleaseBuffer,
ereport) so the negative-INCREMENT case is rejected rather than producing a
negative new_ctr that corrupts the encoded ID.

Introduces a sequence access method framework in Spock plus a
snowflake-ID provider, so nextval() on a participating sequence
yields globally unique, monotonically increasing IDs without
cross-node coordination. Includes a PG18 nextval-hook patch,
catalogue entries for spock 6.0.0-devel, docs, and regress/TAP
coverage.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (8)
docs/distributed_sequences.md (2)

17-19: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replication semantics for spock.sequence_kind still contradict each other.

Lines 17–19 state assignments replicate to all nodes via user_catalog_table, but Lines 80–95 explicitly say alter_sequence_set_kind() is node-local and the row does not replicate (and spock.sequence_kind is intentionally not a user_catalog_table). Then Lines 222–227 swing back to "spock.sequence_kind... is a user_catalog_table replicated to peer nodes", and the security implication built on top of that ("Any role you grant EXECUTE... can change sequence behaviour on every node") only holds under the replicated interpretation. Pick one model and reconcile all three sections plus the security implications; otherwise operators will draw opposite conclusions about cluster-wide uniqueness and the trust model.

Also applies to: 80-95, 222-227

🤖 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 `@docs/distributed_sequences.md` around lines 17 - 19, Choose a single
replication model for spock.sequence_kind and update all references to be
consistent: either document that spock.sequence_kind is a user_catalog_table and
thus replicated to peers (and adjust the description of
alter_sequence_set_kind() to be cluster-visible, plus keep the security
implication that EXECUTE can change behavior across all nodes), or document that
spock.sequence_kind is node-local (and mark alter_sequence_set_kind() as
non-replicated, remove/adjust any claims about cluster-wide changes and the
EXECUTE security scope). Specifically, reconcile and edit the paragraphs
mentioning spock.sequence_kind (lines ~17–19 and ~222–227), the description and
behavior of alter_sequence_set_kind() (lines ~80–95), and the security
implication about EXECUTE so they all state the same replication semantics and
trust model.

140-146: ⚠️ Potential issue | 🟡 Minor | 💤 Low value

Fenced block still missing a language identifier (MD040).

✏️ Proposed fix
-```
+```text
  bit 63        : reserved, always 0 (sign bit, keeps values non-negative)
🤖 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 `@docs/distributed_sequences.md` around lines 140 - 146, The fenced code block
showing the bit-field layout (the lines starting with "bit 63        :
reserved..." through "bits 11..0    : 12-bit counter") lacks a language
identifier; update the opening fence from ``` to ```text so the block becomes a
text code fence and satisfies MD040.
src/spock_seqam.c (2)

564-568: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Past review comment still applies: turn unsupported-state asserts into runtime guards.

In production builds, these Asserts are compiled out. When spock.default_sequence_kind = snowflake, nextval() calls during recovery, extension creation, or binary upgrade would bypass the local fallback and invoke the dispatcher with unsupported state.

Please see the existing review comment for the recommended fix.

🤖 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 `@src/spock_seqam.c` around lines 564 - 568, Replace the compile-time Assert
checks (Assert(!RecoveryInProgress()), Assert(!creating_extension),
Assert(!IsBinaryUpgrade), Assert(kind > SPOCK_SEQAM_LOCAL && kind <
SPOCK_SEQAM_NMETHODS)) with runtime guards that detect these unsupported states
and force a local fallback instead of asserting; specifically, in the
spock_seqam dispatch path where these asserts appear, check if
RecoveryInProgress() || creating_extension || IsBinaryUpgrade || !(kind >
SPOCK_SEQAM_LOCAL && kind < SPOCK_SEQAM_NMETHODS) and if so set kind =
SPOCK_SEQAM_LOCAL (or otherwise route to the local sequence handler) so
nextval() will use the local implementation in those states rather than invoking
the managed dispatcher.

547-556: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Past review comment still applies: revalidate non-local kinds when repopulating the cache.

When spock.default_sequence_kind = snowflake, this path caches a managed kind without rechecking the sequence's compatibility constraints (AS bigint, NO MAXVALUE, CACHE 1, positive INCREMENT). An incompatible or later-altered sequence can reach spock_seqam_snowflake_nextval() with semantics it cannot honor.

Please see the existing review comment for the recommended fix.

🤖 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 `@src/spock_seqam.c` around lines 547 - 556, When repopulating SeqAmCache you
must revalidate any non-local kind before caching to avoid storing an
incompatible managed kind (e.g. when spock_seqam_default_kind == snowflake); add
a validation step after determining kind (between the
seqam_catalog_lookup/seqam_init_cache block and the hash_search) that calls a
sequence-attribute checker (implement a helper such as
spock_seqam_validate_sequence_for_kind(seqoid, kind)) to verify the sequence has
AS bigint, NO MAXVALUE, CACHE 1, positive INCREMENT, etc.; if validation fails,
fall back to a safe kind (e.g. LOCAL or the default-safe kind) and only then
insert into SeqAmCache (entry->kind), so spock_seqam_snowflake_nextval() never
gets an incompatible sequence kind cached.
src/spock_node.c (1)

168-181: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Past review comment still applies: validate Snowflake node-id domain and reject InvalidOid.

The current implementation can return InvalidOid when hash_any() & 0xffff produces 0, and does not validate uniqueness of the low 10 bits used by Snowflake encoding. Two nodes whose IDs differ only outside SPOCK_SNOWFLAKE_NODE_BITS would emit identical Snowflake values.

Please see the existing review comment for the recommended fix.

🤖 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 `@src/spock_node.c` around lines 168 - 181, spock_node_id_for_name currently
returns 0 (InvalidOid) or a value whose low SPOCK_SNOWFLAKE_NODE_BITS are all
zero; change it so after computing raw = (hash_any(...) & 0xffff) you validate
and fix the value: if raw == InvalidOid (0) set raw to a non-zero value (e.g.
1), and if (raw & ((1 << SPOCK_SNOWFLAKE_NODE_BITS) - 1)) == 0 set the low
SPOCK_SNOWFLAKE_NODE_BITS to a non-zero pattern (while preserving the other
bits) before casting to Oid; keep all work inside spock_node_id_for_name and use
the SPOCK_SNOWFLAKE_NODE_BITS constant to build the mask.
src/spock_executor.c (1)

253-258: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore the caller's security context in PG_FINALLY.

At Line 253 and Line 310, the code elevates to BOOTSTRAP_SUPERUSERID but restores the original userid only on the normal return path. If either SeqAM helper throws, the surrounding error handling continues under the elevated context.

Suggested pattern
-				GetUserIdAndSecContext(&save_userid, &save_sec_context);
-				SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
-									   save_sec_context |
-									   SECURITY_LOCAL_USERID_CHANGE);
-				spock_seqam_drop_sequence_record(objectId);
-				SetUserIdAndSecContext(save_userid, save_sec_context);
+				GetUserIdAndSecContext(&save_userid, &save_sec_context);
+				PG_TRY();
+				{
+					SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
+										   save_sec_context |
+										   SECURITY_LOCAL_USERID_CHANGE);
+					spock_seqam_drop_sequence_record(objectId);
+				}
+				PG_FINALLY();
+				{
+					SetUserIdAndSecContext(save_userid, save_sec_context);
+				}
+				PG_END_TRY();

Apply the same pattern to the spock_seqam_relocate_sequence_record(objectId) block.

Also applies to: 310-315

🤖 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 `@src/spock_executor.c` around lines 253 - 258, The code elevates privileges
using GetUserIdAndSecContext/SetUserIdAndSecContext before calling
spock_seqam_drop_sequence_record and spock_seqam_relocate_sequence_record but
only restores the original context on the normal path; if the SeqAM helpers
throw, the elevated context remains. Fix both blocks by wrapping the
SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID...) and the subsequent
spock_seqam_* call in a PG_TRY { ... } PG_FINALLY {
SetUserIdAndSecContext(save_userid, save_sec_context); } PG_END_TRY; so the
original user/security context is always restored (use the same
save_userid/save_sec_context variables and apply the pattern around
spock_seqam_drop_sequence_record(objectId) and
spock_seqam_relocate_sequence_record(objectId)).
sql/spock--6.0.0.sql (1)

391-397: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Build sequence_name from (nspname, relname), not cached seqoid.

Lines 328-330 already document that seqoid is stale after logical restore until a later refresh runs. Using sk.seqoid::regclass here means spock.sequence_info can show the wrong object, or just an OID string, during exactly that window.

Suggested fix
 CREATE VIEW spock.sequence_info AS
 SELECT
-	sk.seqoid::regclass AS sequence_name,
+	to_regclass(format('%I.%I', sk.nspname, sk.relname)) AS sequence_name,
 	sk.kind,
 	CASE WHEN spock.sequence_hook_available() THEN 'active'
 	     ELSE 'inactive' END AS hook_status
 FROM spock.sequence_kind sk;
🤖 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 `@sql/spock--6.0.0.sql` around lines 391 - 397, Replace the use of the cached
OID field sk.seqoid::regclass in the spock.sequence_info view with a name-based
construction from sk.nspname and sk.relname so the view resolves the correct
object after logical restore; update the SELECT to build the sequence_name from
(nspname, relname) and cast that to regclass (or use format/qualified identifier
construction) instead of using seqoid, keeping the rest of the view (kind and
hook_status via spock.sequence_hook_available()) unchanged and still selecting
from spock.sequence_kind sk.
src/spock_seqam_snowflake.c (1)

147-156: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Decode the stored timestamp with the full Snowflake shift.

Line 147 still unpacks last_value as if the timestamp starts immediately above the counter bits. The write path stores new_ts << SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT, so sf_unpack() reads the wrong bit range and corrupts cur_ts, which then feeds the monotonicity and WAL-reservation logic.

Suggested fix
-#define SF_STATE_TS_SHIFT		SPOCK_SNOWFLAKE_COUNTER_BITS
+#define SF_STATE_TS_SHIFT		SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT
 `#define` SF_STATE_CTR_MASK		SPOCK_SNOWFLAKE_COUNTER_MASK
 `#define` SF_STATE_TS_MASK		(((UINT64CONST(1) << SPOCK_SNOWFLAKE_TIMESTAMP_BITS) - 1) \
 								 << SF_STATE_TS_SHIFT)
🤖 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 `@src/spock_seqam_snowflake.c` around lines 147 - 156, sf_unpack is extracting
the timestamp using SF_STATE_TS_SHIFT (currently set to
SPOCK_SNOWFLAKE_COUNTER_BITS) which doesn't match the write path that stores
timestamps shifted by SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT; update the unpacking to
use the full Snowflake timestamp shift/mask so the same bit range is decoded.
Specifically, ensure SF_STATE_TS_SHIFT (or the shift used inside sf_unpack)
equals SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT and that SF_STATE_TS_MASK covers
(SPOCK_SNOWFLAKE_TIMESTAMP_BITS << SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT), then
extract *ts by shifting the packed value by SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT and
keep *ctr extracted with SF_STATE_CTR_MASK so cur_ts and counter are decoded
from the same bit layout used by the write path.
🧹 Nitpick comments (3)
include/spock_seqam.h (2)

13-16: 💤 Low value

postgres.h in a public header is non-idiomatic for PG.

PostgreSQL convention is that postgres.h is included as the first include of every .c file, and headers assume it has already been included. Including it from a public header works (header guards make it a no-op when callers do the right thing) but it leaks the assumption and can interact poorly with downstream tooling that expects header order to be control­lable. Consider documenting "include after postgres.h" instead and dropping the include here.

🤖 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 `@include/spock_seqam.h` around lines 13 - 16, Remove the `#include` "postgres.h"
from the public header and instead add a short comment at the top of
include/spock_seqam.h stating that this header must be included only after
postgres.h has been included (so callers include postgres.h first in their .c
files); keep the other includes ("catalog/pg_sequence.h", "commands/sequence.h",
"utils/relcache.h") as-is and ensure any .c files that currently rely on this
header still include postgres.h first (or add postgres.h to those .c files) so
symbols used by SeqTable and nextval_hook_type from commands/sequence.h resolve
correctly.

100-104: 💤 Low value

Inconsistent mask convention between counter and node masks.

SPOCK_SNOWFLAKE_COUNTER_MASK is the raw 12-bit mask (0xFFF), but SPOCK_SNOWFLAKE_NODE_MASK is pre-shifted into its in-value position (0x3FF << 12). Consumers must remember to write value & COUNTER_MASK for one and (value & NODE_MASK) >> NODE_SHIFT for the other, which is easy to mix up. Consider one consistent convention — either both masks pre-shifted (and add explicit _SHIFT helpers for extraction) or both raw with an explicit shift at the call site.

Also, SPOCK_SNOWFLAKE_MAX_NODE_ID on line 104 uses untyped 1 while the masks above use UINT64CONST(1); harmless at the current bit width but stylistically inconsistent and would silently overflow if NODE_BITS ever grew past 30.

🤖 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 `@include/spock_seqam.h` around lines 100 - 104, SPOCK_SNOWFLAKE_COUNTER_MASK
and SPOCK_SNOWFLAKE_NODE_MASK use inconsistent conventions (counter is raw, node
is pre-shifted) which is error-prone; change the masks so they follow the same
convention (prefer both raw/unshifted): redefine SPOCK_SNOWFLAKE_NODE_MASK as
(((UINT64CONST(1) << SPOCK_SNOWFLAKE_NODE_BITS) - 1)) (no shift) and add a new
helper SPOCK_SNOWFLAKE_NODE_MASK_SHIFTED = (SPOCK_SNOWFLAKE_NODE_MASK <<
SPOCK_SNOWFLAKE_NODE_SHIFT) for in-value masking/typing, and also make
SPOCK_SNOWFLAKE_MAX_NODE_ID use UINT64CONST(1) ((UINT64CONST(1) <<
SPOCK_SNOWFLAKE_NODE_BITS) - 1) to keep typing consistent; update any consumers
that expect the old pre-shifted NODE_MASK to use the new _SHIFTED helper or
perform the explicit shift.
patches/15/pg15-050-nextval-hook.diff (1)

95-134: 💤 Low value

Minor: *last written inside the inner loop may inhibit register promotion.

The body of the reservation loop now writes *last = next; on every iteration (line 100) where before this was a register-friendly local. The compiler can't generally hoist this because last is a pointer with potential aliasing. With default cache=1 this is a non-issue, but for sequences with a large CACHE setting it's a measurable difference under hot-path nextval traffic.

Consider keeping a local mirror and assigning to *last once after the loop:

♻️ Sketch
+	int64		last_local;
 ...
-	*last = next = result = seq->last_value;
+	last_local = next = result = seq->last_value;
 ...
-			*last = next;
+			last_local = next;
 ...
-	seq->last_value = *last;	/* last fetched number */
+	*last = last_local;
+	seq->last_value = last_local;	/* last fetched number */
🤖 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 `@patches/15/pg15-050-nextval-hook.diff` around lines 95 - 134, In
nextval_internal, avoid writing through the pointer last on every loop iteration
(which prevents register promotion); instead introduce a local scalar (e.g.,
last_local) to mirror the current "last" value inside the reservation loop
(update last_local = next and use last_local for comparisons), and after the
loop assign the pointer once with *last = last_local; ensure subsequent uses
that expect the pointer-updated value (e.g., seq->last_value = *last) are
adjusted to use last_local or performed after the single write so semantics
remain unchanged.
🤖 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 `@patches/15/pg15-050-nextval-hook.diff`:
- Around line 25-50: In nextval_internal, initialize the local variable last
defensively before passing &last to nextval_hook or sequence_am_local_nextval to
avoid leaving it uninitialized if a hook returns without setting *last; locate
the nextval_internal function and add a single initialization of last (e.g., set
to the returned value sentinel or the incoming last value semantics) immediately
before the block that calls nextval_hook/sequence_am_local_nextval so that
subsequent use (elm->cached = last) cannot read uninitialized memory.

In `@tests/tap/schedule`:
- Line 48: The schedule change only added a blank line (at the location near
line 48) but did not add the expected TAP test entry for Snowflake SeqAM; either
insert the intended test entry key (for example add a line like test:
0NN_snowflake_seqam) into the schedule file so the Snowflake SeqAM regression is
enabled, or revert/remove the stray blank-line edit and update the PR/stack
description to reflect that no TAP test was added; locate and modify the
schedule file where other test: entries are defined (search for existing "test:"
lines) to place the new entry consistent with formatting.

---

Duplicate comments:
In `@docs/distributed_sequences.md`:
- Around line 17-19: Choose a single replication model for spock.sequence_kind
and update all references to be consistent: either document that
spock.sequence_kind is a user_catalog_table and thus replicated to peers (and
adjust the description of alter_sequence_set_kind() to be cluster-visible, plus
keep the security implication that EXECUTE can change behavior across all
nodes), or document that spock.sequence_kind is node-local (and mark
alter_sequence_set_kind() as non-replicated, remove/adjust any claims about
cluster-wide changes and the EXECUTE security scope). Specifically, reconcile
and edit the paragraphs mentioning spock.sequence_kind (lines ~17–19 and
~222–227), the description and behavior of alter_sequence_set_kind() (lines
~80–95), and the security implication about EXECUTE so they all state the same
replication semantics and trust model.
- Around line 140-146: The fenced code block showing the bit-field layout (the
lines starting with "bit 63        : reserved..." through "bits 11..0    :
12-bit counter") lacks a language identifier; update the opening fence from ```
to ```text so the block becomes a text code fence and satisfies MD040.

In `@sql/spock--6.0.0.sql`:
- Around line 391-397: Replace the use of the cached OID field
sk.seqoid::regclass in the spock.sequence_info view with a name-based
construction from sk.nspname and sk.relname so the view resolves the correct
object after logical restore; update the SELECT to build the sequence_name from
(nspname, relname) and cast that to regclass (or use format/qualified identifier
construction) instead of using seqoid, keeping the rest of the view (kind and
hook_status via spock.sequence_hook_available()) unchanged and still selecting
from spock.sequence_kind sk.

In `@src/spock_executor.c`:
- Around line 253-258: The code elevates privileges using
GetUserIdAndSecContext/SetUserIdAndSecContext before calling
spock_seqam_drop_sequence_record and spock_seqam_relocate_sequence_record but
only restores the original context on the normal path; if the SeqAM helpers
throw, the elevated context remains. Fix both blocks by wrapping the
SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID...) and the subsequent
spock_seqam_* call in a PG_TRY { ... } PG_FINALLY {
SetUserIdAndSecContext(save_userid, save_sec_context); } PG_END_TRY; so the
original user/security context is always restored (use the same
save_userid/save_sec_context variables and apply the pattern around
spock_seqam_drop_sequence_record(objectId) and
spock_seqam_relocate_sequence_record(objectId)).

In `@src/spock_node.c`:
- Around line 168-181: spock_node_id_for_name currently returns 0 (InvalidOid)
or a value whose low SPOCK_SNOWFLAKE_NODE_BITS are all zero; change it so after
computing raw = (hash_any(...) & 0xffff) you validate and fix the value: if raw
== InvalidOid (0) set raw to a non-zero value (e.g. 1), and if (raw & ((1 <<
SPOCK_SNOWFLAKE_NODE_BITS) - 1)) == 0 set the low SPOCK_SNOWFLAKE_NODE_BITS to a
non-zero pattern (while preserving the other bits) before casting to Oid; keep
all work inside spock_node_id_for_name and use the SPOCK_SNOWFLAKE_NODE_BITS
constant to build the mask.

In `@src/spock_seqam_snowflake.c`:
- Around line 147-156: sf_unpack is extracting the timestamp using
SF_STATE_TS_SHIFT (currently set to SPOCK_SNOWFLAKE_COUNTER_BITS) which doesn't
match the write path that stores timestamps shifted by
SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT; update the unpacking to use the full Snowflake
timestamp shift/mask so the same bit range is decoded. Specifically, ensure
SF_STATE_TS_SHIFT (or the shift used inside sf_unpack) equals
SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT and that SF_STATE_TS_MASK covers
(SPOCK_SNOWFLAKE_TIMESTAMP_BITS << SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT), then
extract *ts by shifting the packed value by SPOCK_SNOWFLAKE_TIMESTAMP_SHIFT and
keep *ctr extracted with SF_STATE_CTR_MASK so cur_ts and counter are decoded
from the same bit layout used by the write path.

In `@src/spock_seqam.c`:
- Around line 564-568: Replace the compile-time Assert checks
(Assert(!RecoveryInProgress()), Assert(!creating_extension),
Assert(!IsBinaryUpgrade), Assert(kind > SPOCK_SEQAM_LOCAL && kind <
SPOCK_SEQAM_NMETHODS)) with runtime guards that detect these unsupported states
and force a local fallback instead of asserting; specifically, in the
spock_seqam dispatch path where these asserts appear, check if
RecoveryInProgress() || creating_extension || IsBinaryUpgrade || !(kind >
SPOCK_SEQAM_LOCAL && kind < SPOCK_SEQAM_NMETHODS) and if so set kind =
SPOCK_SEQAM_LOCAL (or otherwise route to the local sequence handler) so
nextval() will use the local implementation in those states rather than invoking
the managed dispatcher.
- Around line 547-556: When repopulating SeqAmCache you must revalidate any
non-local kind before caching to avoid storing an incompatible managed kind
(e.g. when spock_seqam_default_kind == snowflake); add a validation step after
determining kind (between the seqam_catalog_lookup/seqam_init_cache block and
the hash_search) that calls a sequence-attribute checker (implement a helper
such as spock_seqam_validate_sequence_for_kind(seqoid, kind)) to verify the
sequence has AS bigint, NO MAXVALUE, CACHE 1, positive INCREMENT, etc.; if
validation fails, fall back to a safe kind (e.g. LOCAL or the default-safe kind)
and only then insert into SeqAmCache (entry->kind), so
spock_seqam_snowflake_nextval() never gets an incompatible sequence kind cached.

---

Nitpick comments:
In `@include/spock_seqam.h`:
- Around line 13-16: Remove the `#include` "postgres.h" from the public header and
instead add a short comment at the top of include/spock_seqam.h stating that
this header must be included only after postgres.h has been included (so callers
include postgres.h first in their .c files); keep the other includes
("catalog/pg_sequence.h", "commands/sequence.h", "utils/relcache.h") as-is and
ensure any .c files that currently rely on this header still include postgres.h
first (or add postgres.h to those .c files) so symbols used by SeqTable and
nextval_hook_type from commands/sequence.h resolve correctly.
- Around line 100-104: SPOCK_SNOWFLAKE_COUNTER_MASK and
SPOCK_SNOWFLAKE_NODE_MASK use inconsistent conventions (counter is raw, node is
pre-shifted) which is error-prone; change the masks so they follow the same
convention (prefer both raw/unshifted): redefine SPOCK_SNOWFLAKE_NODE_MASK as
(((UINT64CONST(1) << SPOCK_SNOWFLAKE_NODE_BITS) - 1)) (no shift) and add a new
helper SPOCK_SNOWFLAKE_NODE_MASK_SHIFTED = (SPOCK_SNOWFLAKE_NODE_MASK <<
SPOCK_SNOWFLAKE_NODE_SHIFT) for in-value masking/typing, and also make
SPOCK_SNOWFLAKE_MAX_NODE_ID use UINT64CONST(1) ((UINT64CONST(1) <<
SPOCK_SNOWFLAKE_NODE_BITS) - 1) to keep typing consistent; update any consumers
that expect the old pre-shifted NODE_MASK to use the new _SHIFTED helper or
perform the explicit shift.

In `@patches/15/pg15-050-nextval-hook.diff`:
- Around line 95-134: In nextval_internal, avoid writing through the pointer
last on every loop iteration (which prevents register promotion); instead
introduce a local scalar (e.g., last_local) to mirror the current "last" value
inside the reservation loop (update last_local = next and use last_local for
comparisons), and after the loop assign the pointer once with *last =
last_local; ensure subsequent uses that expect the pointer-updated value (e.g.,
seq->last_value = *last) are adjusted to use last_local or performed after the
single write so semantics remain unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b48e069a-81aa-42ce-8aa0-d57f30e452fa

📥 Commits

Reviewing files that changed from the base of the PR and between 2013009 and 5ef394c.

⛔ Files ignored due to path filters (1)
  • tests/regress/expected/init.out is excluded by !**/*.out
📒 Files selected for processing (22)
  • docs/distributed_sequences.md
  • include/spock_node.h
  • include/spock_seqam.h
  • mkdocs.yml
  • patches/15/pg15-050-nextval-hook.diff
  • patches/16/pg16-050-nextval-hook.diff
  • patches/17/pg17-050-nextval-hook.diff
  • patches/18/pg18-050-nextval-hook.diff
  • sql/spock--6.0.0.sql
  • src/compat/15/spock_compat.h
  • src/compat/16/spock_compat.h
  • src/compat/17/spock_compat.h
  • src/compat/18/spock_compat.h
  • src/spock.c
  • src/spock_apply.c
  • src/spock_executor.c
  • src/spock_node.c
  • src/spock_seqam.c
  • src/spock_seqam_snowflake.c
  • src/spock_sequences.c
  • tests/regress/sql/init.sql
  • tests/tap/schedule
💤 Files with no reviewable changes (4)
  • src/compat/16/spock_compat.h
  • src/compat/17/spock_compat.h
  • src/compat/15/spock_compat.h
  • src/compat/18/spock_compat.h
✅ Files skipped from review due to trivial changes (1)
  • mkdocs.yml

Comment on lines +25 to +50
int64 incby,
maxv,
minv,
cache,
- log,
- fetch,
last;
- int64 result,
- next,
- rescnt = 0;
+ int64 result;
bool cycle;
- bool logit = false;

/* open and lock sequence */
init_sequence(relid, &elm, &seqrel);
@@ -699,12 +693,51 @@ nextval_internal(Oid relid, bool check_permissions)
cycle = pgsform->seqcycle;
ReleaseSysCache(pgstuple);

+ /* Spock: hook overrides; swap for rel->rd_sequenceam->nextval at SeqAM merge. */
+ if (nextval_hook != NULL)
+ result = (*nextval_hook) (seqrel, incby, maxv, minv, cache, cycle, &last);
+ else
+ result = sequence_am_local_nextval(seqrel, incby, maxv, minv,
+ cache, cycle, &last);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Initialize last defensively before dispatching to the hook.

last is declared (line 31) but never initialized before &last is passed to nextval_hook at line 47. The local-case helper writes *last unconditionally on entry, but a third-party hook that returns without writing *last (e.g. an early-return error path that returns a sentinel) will cause elm->cached = last; a few lines below to read uninitialized stack memory and poison the in-core CACHE fast path for the remainder of the session. The header comment documents the contract ("Methods that do not prefetch ... must set *last to the returned value") but the cost of a defensive init is one line.

🛡️ Suggested defensive init
 	int64		incby,
 				maxv,
 				minv,
 				cache,
-				last;
+				last = 0;
 	int64		result;
🤖 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 `@patches/15/pg15-050-nextval-hook.diff` around lines 25 - 50, In
nextval_internal, initialize the local variable last defensively before passing
&last to nextval_hook or sequence_am_local_nextval to avoid leaving it
uninitialized if a hook returns without setting *last; locate the
nextval_internal function and add a single initialization of last (e.g., set to
the returned value sentinel or the incoming last value semantics) immediately
before the block that calls nextval_hook/sequence_am_local_nextval so that
subsequent use (elm->cached = last) cannot read uninitialized memory.

Comment thread tests/tap/schedule
test: 022_rmgr_progress_post_checkpoint_crash
# Upgrade schema match test (builds from source, slow):
#test: 018_upgrade_schema_match

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Schedule change does not match the PR/stack description.

The stack context for this cohort states this file should "enable the Snowflake SeqAM regression test in TAP schedule", but the only change here is an added blank line at 48 — no new test: entry was introduced. Either add the intended TAP test entry for the Snowflake SeqAM coverage (e.g. test: 0NN_snowflake_seqam) or drop the stray whitespace change and update the stack/PR summary accordingly.

🤖 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 `@tests/tap/schedule` at line 48, The schedule change only added a blank line
(at the location near line 48) but did not add the expected TAP test entry for
Snowflake SeqAM; either insert the intended test entry key (for example add a
line like test: 0NN_snowflake_seqam) into the schedule file so the Snowflake
SeqAM regression is enabled, or revert/remove the stray blank-line edit and
update the PR/stack description to reflect that no TAP test was added; locate
and modify the schedule file where other test: entries are defined (search for
existing "test:" lines) to place the new entry consistent with formatting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant