Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ vpath % src src/compat/$(PGVER)
DATA = $(wildcard sql/$(EXTENSION)*--*.sql)
SRCS := $(wildcard src/*.c) \
$(wildcard src/compat/$(PGVER)/*.c)

OBJS = $(filter-out src/spock_output.o, $(SRCS:.c=.o))

PG_CPPFLAGS += -I$(libpq_srcdir) \
Expand Down Expand Up @@ -64,7 +65,7 @@ REGRESS = preseed infofuncs init_fail init preseed_check basic conflict_secondar
row_filter_sampling att_list column_filter apply_delay alter_options \
extended node_origin_cascade multiple_upstreams tuple_origin autoddl \
sync_event sync_table generated_columns spill_transaction read_only \
resolutions_retention drop
resolutions_retention version_guard drop

# The following test cases are disabled while developing.
#
Expand Down
51 changes: 37 additions & 14 deletions docs/conflicts.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,47 @@ conflicts with the following logic:
Note that on a conflicting transaction, the delta-apply column will be
correctly calculated and applied.

To make a column a conflict-free delta-apply column, ensuring that the value
replicated is the delta of the committed changes (the old value plus or
minus any new value) to a given record, you need to apply the following
settings to the column: `log_old_value=true,
delta_apply_function=spock.delta_apply`. For example:
To make a column a conflict-free delta-apply column, ensuring that the
value replicated is the delta of the committed changes (the old value
plus or minus any new value) to a given record, attach a SECURITY LABEL
to the column via the `spock.delta_apply()` helper:

```sql
ALTER TABLE pgbench_accounts ALTER COLUMN abalance
SET (log_old_value=true, delta_apply_function=spock.delta_apply);
ALTER TABLE pgbench_branches ALTER COLUMN bbalance
SET (log_old_value=true, delta_apply_function=spock.delta_apply);
ALTER TABLE pgbench_tellers ALTER COLUMN tbalance
SET (log_old_value=true, delta_apply_function=spock.delta_apply);
SELECT spock.delta_apply('pgbench_accounts'::regclass, 'abalance');
SELECT spock.delta_apply('pgbench_branches'::regclass, 'bbalance');
SELECT spock.delta_apply('pgbench_tellers'::regclass, 'tbalance');
```

As a special safety-valve feature, if you ever need to re-set a
`log_old_value` column you can temporarily alter the column to
`log_old_value` is `false`.
To remove the marker, pass `to_drop => true`:

```sql
SELECT spock.delta_apply('pgbench_accounts'::regclass, 'abalance', to_drop => true);
```

Under the hood, `spock.delta_apply()` writes a row into `pg_seclabel`
with `provider = 'spock'` and `label = 'spock.delta_apply'`. The
binary-upgrade compatibility shim that translates legacy spock 5.x
reloptions during `pg_upgrade` writes the same canonical label, so
operators can audit the catalog uniformly:

```sql
SELECT * FROM pg_seclabel
WHERE provider = 'spock' AND label = 'spock.delta_apply';
Comment thread
danolivo marked this conversation as resolved.
```

### Upgrading from spock 5.x

Spock 5.x recorded the same intent as a pair of per-attribute reloptions
(`log_old_value=true, delta_apply_function=spock.delta_apply`). During
`pg_upgrade`, the binary-upgrade compatibility shim translates those
reloptions into the new `SECURITY LABEL` form automatically. Look for
`NOTICE: spock: rewrote ALTER TABLE … ALTER COLUMN … legacy options to
SECURITY LABEL` lines in `pg_upgrade.log` to audit the translation.
After the upgrade, `SELECT * FROM pg_seclabel WHERE provider = 'spock'
AND label = 'spock.delta_apply'` is the authoritative list of delta-apply
columns. See
[Binary-upgrade compatibility shims](internals-doc/binary-upgrade-compat-shim.md)
for the full design.

### Conflict Configuration Options

Expand Down
3 changes: 1 addition & 2 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,7 @@ ALTER TABLE table_name ALTER COLUMN column_name SET NOT NULL;
Then configure the Delta-Apply column with the following command:

```sql
ALTER TABLE table_name ALTER COLUMN column_name
SET (log_old_value=true, delta_apply_function=spock.delta_apply);
SELECT spock.delta_apply('table_name'::regclass, 'column_name');
```

## Configuration Issues
Expand Down
3 changes: 3 additions & 0 deletions include/spock.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,7 @@ VALGRIND_PRINTF(const char *format,...)

extern void spock_init_failover_slot(void);

/* lives in src/spock_bucompat_5x.c -- used only under IsBinaryUpgrade */
extern void register_spock_compat_5x(void);

#endif /* SPOCK_H */
32 changes: 32 additions & 0 deletions patches/15/pg15-000-spock-patchset-version.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Spock core-patchset: export patchset version via miscadmin.h and globals.c.

Adds SPOCK_CORE_PATCHSET_VERSION (compile-time constant) and
SpockCorePatchsetVersion (runtime global) to the standard places
PostgreSQL already uses for server-wide state. No new files.

--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -498,4 +498,11 @@
/* in executor/nodeHash.c */
extern size_t get_hash_memory_limit(void);

+/*
+ * Spock core-patchset identity. Bump the version when the patchset
+ * changes in a way visible to the extension binary.
+ */
+#define SPOCK_CORE_PATCHSET_VERSION 1
+extern PGDLLIMPORT int SpockCorePatchsetVersion;
+
#endif /* MISCADMIN_H */
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -114,6 +114,9 @@
bool IsBinaryUpgrade = false;
bool IsBackgroundWorker = false;

+/* Spock core-patchset identity. */
+int SpockCorePatchsetVersion = SPOCK_CORE_PATCHSET_VERSION;
+
bool ExitOnAnyError = false;

int DateStyle = USE_ISO_DATES;
32 changes: 32 additions & 0 deletions patches/16/pg16-000-spock-patchset-version.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Spock core-patchset: export patchset version via miscadmin.h and globals.c.

Adds SPOCK_CORE_PATCHSET_VERSION (compile-time constant) and
SpockCorePatchsetVersion (runtime global) to the standard places
PostgreSQL already uses for server-wide state. No new files.

--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -510,4 +510,11 @@
/* in executor/nodeHash.c */
extern size_t get_hash_memory_limit(void);

+/*
+ * Spock core-patchset identity. Bump the version when the patchset
+ * changes in a way visible to the extension binary.
+ */
+#define SPOCK_CORE_PATCHSET_VERSION 1
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it is smart idea in general. It does replace one patch with another though (but smaller). I am thinking one day we could have a spock-lite with reduced functionality that would work with plain Postgres. I will think about this some more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@mason-sharp ,
It is trivial to guard our patches and the corresponding Spock machinery. That enables Spock to be used with upstream Postgres. But for production use, at least one patch (logical clock) is a requirement. So, for the safety and predictability, we should have a mechanism to detect patched postgres.

+extern PGDLLIMPORT int SpockCorePatchsetVersion;
+
#endif /* MISCADMIN_H */
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -114,6 +114,9 @@
bool IsBinaryUpgrade = false;
bool IsBackgroundWorker = false;

+/* Spock core-patchset identity. */
+int SpockCorePatchsetVersion = SPOCK_CORE_PATCHSET_VERSION;
+
bool ExitOnAnyError = false;

int DateStyle = USE_ISO_DATES;
32 changes: 32 additions & 0 deletions patches/17/pg17-000-spock-patchset-version.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Spock core-patchset: export patchset version via miscadmin.h and globals.c.

Adds SPOCK_CORE_PATCHSET_VERSION (compile-time constant) and
SpockCorePatchsetVersion (runtime global) to the standard places
PostgreSQL already uses for server-wide state. No new files.

--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -525,4 +525,11 @@
/* in executor/nodeHash.c */
extern size_t get_hash_memory_limit(void);

+/*
+ * Spock core-patchset identity. Bump the version when the patchset
+ * changes in a way visible to the extension binary.
+ */
+#define SPOCK_CORE_PATCHSET_VERSION 1
+extern PGDLLIMPORT int SpockCorePatchsetVersion;
+
#endif /* MISCADMIN_H */
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -117,6 +117,9 @@
bool IsUnderPostmaster = false;
bool IsBinaryUpgrade = false;

+/* Spock core-patchset identity. */
+int SpockCorePatchsetVersion = SPOCK_CORE_PATCHSET_VERSION;
+
bool ExitOnAnyError = false;

int DateStyle = USE_ISO_DATES;
32 changes: 32 additions & 0 deletions patches/18/pg18-000-spock-patchset-version.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Spock core-patchset: export patchset version via miscadmin.h and globals.c.

Adds SPOCK_CORE_PATCHSET_VERSION (compile-time constant) and
SpockCorePatchsetVersion (runtime global) to the standard places
PostgreSQL already uses for server-wide state. No new files.

--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -540,4 +540,11 @@
/* in executor/nodeHash.c */
extern size_t get_hash_memory_limit(void);

+/*
+ * Spock core-patchset identity. Bump the version when the patchset
+ * changes in a way visible to the extension binary.
+ */
+#define SPOCK_CORE_PATCHSET_VERSION 1
+extern PGDLLIMPORT int SpockCorePatchsetVersion;
+
#endif /* MISCADMIN_H */
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -120,6 +120,9 @@
bool IsUnderPostmaster = false;
bool IsBinaryUpgrade = false;

+/* Spock core-patchset identity. */
+int SpockCorePatchsetVersion = SPOCK_CORE_PATCHSET_VERSION;
+
bool ExitOnAnyError = false;

int DateStyle = USE_ISO_DATES;
163 changes: 163 additions & 0 deletions sql/spock--5.0.6--5.0.7.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@

/* spock--5.0.6--5.0.7.sql */

-- complain if script is sourced in psql, rather than via ALTER EXTENSION
\echo Use "ALTER EXTENSION spock UPDATE TO '5.0.7'" to load this file. \quit

CREATE FUNCTION spock.pause_apply_workers()
RETURNS void VOLATILE LANGUAGE c AS 'MODULE_PATHNAME', 'spock_pause_apply_workers';

CREATE FUNCTION spock.resume_apply_workers()
RETURNS void VOLATILE LANGUAGE c AS 'MODULE_PATHNAME', 'spock_resume_apply_workers';

REVOKE EXECUTE ON FUNCTION spock.pause_apply_workers() FROM PUBLIC;
REVOKE EXECUTE ON FUNCTION spock.resume_apply_workers() FROM PUBLIC;

DROP PROCEDURE IF EXISTS spock.wait_for_sync_event(OUT bool, oid, pg_lsn, int);
DROP PROCEDURE IF EXISTS spock.wait_for_sync_event(OUT bool, oid, pg_lsn, int, bool);
DROP PROCEDURE IF EXISTS spock.wait_for_sync_event(OUT bool, name, pg_lsn, int);
DROP PROCEDURE IF EXISTS spock.wait_for_sync_event(OUT bool, name, pg_lsn, int, bool);
CREATE PROCEDURE spock.wait_for_sync_event(
OUT result bool,
origin_id oid,
lsn pg_lsn,
timeout int DEFAULT 0,
wait_if_disabled bool DEFAULT false
) AS $$
DECLARE
target_id oid;
start_time timestamptz := clock_timestamp();
progress_lsn pg_lsn;
sub_is_enabled bool;
sub_slot name;
BEGIN
IF origin_id IS NULL THEN
RAISE EXCEPTION 'Invalid NULL origin_id';
END IF;
target_id := node_id FROM spock.node_info();

-- Upfront existence check is skipped when wait_if_disabled is true because
-- the subscription may not yet exist (e.g. a newly added node whose
-- subscriptions are still initializing). The loop below handles both the
-- not-found and disabled cases gracefully in that mode.
IF NOT wait_if_disabled THEN
SELECT sub_enabled, sub_slot_name INTO sub_is_enabled, sub_slot
FROM spock.subscription
WHERE sub_origin = origin_id AND sub_target = target_id;

IF NOT FOUND THEN
RAISE EXCEPTION 'No subscription found for replication % => %',
origin_id, target_id;
END IF;
END IF;

WHILE true LOOP
-- Re-check subscription state each iteration. Also re-fetches
-- sub_slot_name so the loop is self-contained when wait_if_disabled
-- is true and the pre-loop check was skipped.
SELECT sub_enabled, sub_slot_name INTO sub_is_enabled, sub_slot
FROM spock.subscription
WHERE sub_origin = origin_id AND sub_target = target_id;

IF NOT FOUND THEN
IF NOT wait_if_disabled THEN
RAISE EXCEPTION 'No subscription found for replication % => %',
origin_id, target_id;
END IF;
-- Subscription not yet created; fall through to sleep.
ELSIF NOT sub_is_enabled THEN
IF NOT wait_if_disabled THEN
RAISE EXCEPTION 'Subscription % => % has been disabled',
origin_id, target_id;
END IF;
-- Subscription still initializing; fall through to sleep.
ELSE
-- Subscription is enabled; check LSN progress.
-- Uses PostgreSQL's native origin tracking rather than spock.progress
SELECT remote_lsn INTO progress_lsn
FROM pg_replication_origin_status
WHERE external_id = sub_slot;

IF progress_lsn IS NOT NULL AND progress_lsn >= lsn THEN
result = true;
RETURN;
END IF;
END IF;

IF timeout <> 0 AND
EXTRACT(EPOCH FROM (clock_timestamp() - start_time)) >= timeout THEN
result := false;
RETURN;
END IF;

ROLLBACK;
PERFORM pg_sleep(0.2);
END LOOP;
END;
$$ LANGUAGE plpgsql;

CREATE PROCEDURE spock.wait_for_sync_event(
OUT result bool,
origin name,
lsn pg_lsn,
timeout int DEFAULT 0,
wait_if_disabled bool DEFAULT false
) AS $$
DECLARE
origin_id oid;
BEGIN
origin_id := node_id FROM spock.node WHERE node_name = origin;
IF origin_id IS NULL THEN
RAISE EXCEPTION 'Origin node ''%'' not found', origin;
END IF;
CALL spock.wait_for_sync_event(result, origin_id, lsn, timeout, wait_if_disabled);
END;
$$ LANGUAGE plpgsql;

-- spock.sync_event() gained an optional 'transactional' boolean argument
-- (default false). Drop the old zero-arg signature first so the upgrade
-- doesn't leave behind two overloads with overlapping zero-arg resolution.
DROP FUNCTION IF EXISTS spock.sync_event();
CREATE FUNCTION spock.sync_event(transactional boolean DEFAULT false)
RETURNS pg_lsn RETURNS NULL ON NULL INPUT
AS 'MODULE_PATHNAME', 'spock_create_sync_event'
LANGUAGE C VOLATILE;

/*
* Correct the declared type of spock.subscription.sub_skip_schema.
*
* The column was added as text in the 5.0.1--5.0.2 upgrade, but the C code
* has always treated it as text[] on both read and write paths
* (strlist_to_textarray on write, DatumGetArrayTypeP on read). The bytes
* already on disk are therefore a valid ArrayType; only the catalog's type
* label is wrong. ALTER TABLE ... ALTER COLUMN TYPE text[] USING ... is
* not viable here: there is no SQL expression that converts "varlena bytes
* the planner believes are text but are in fact ArrayType internal format"
* back into an ArrayType Datum. Relabel the column in place so SQL-level
* access (SELECT, unnest, etc.) works as users expect, without rewriting
* data.
*/
LOCK TABLE spock.subscription IN ACCESS EXCLUSIVE MODE;

UPDATE pg_catalog.pg_attribute
SET atttypid = 'text[]'::regtype,
attndims = 1
WHERE attrelid = 'spock.subscription'::regclass
AND attname = 'sub_skip_schema'
AND atttypid = 'text'::regtype;

/*
* Drop any pg_statistic rows for the column. Stats sampled when the
* column was labelled text encode varlena bytes with text semantics;
* after the relabel the planner would interpret the same stavalues
* arrays as text[], producing nonsense selectivities (and possibly
* crashing on operators that validate ArrayType structure). ANALYZE
* will repopulate as needed.
*/
DELETE FROM pg_catalog.pg_statistic
WHERE starelid = 'spock.subscription'::regclass
AND staattnum = (
SELECT attnum
FROM pg_catalog.pg_attribute
WHERE attrelid = 'spock.subscription'::regclass
AND attname = 'sub_skip_schema');
Comment thread
danolivo marked this conversation as resolved.
6 changes: 6 additions & 0 deletions sql/spock--5.0.7--5.0.8.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* spock--5.0.7--5.0.8.sql */

-- complain if script is sourced in psql, rather than via ALTER EXTENSION
\echo Use "ALTER EXTENSION spock UPDATE TO '5.0.8'" to load this file. \quit

-- No schema changes in 5.0.8.
Loading
Loading