diff --git a/Makefile b/Makefile index f8d5cc4d..000138d7 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,9 @@ ifdef SPOCK_RANDOM_DELAYS PG_CPPFLAGS += -DSPOCK_RANDOM_DELAYS endif SHLIB_LINK += $(libpq) $(filter -lintl, $(LIBS)) +ifdef NO_LOG_OLD_VALUE +PG_CPPFLAGS += -DNO_LOG_OLD_VALUE +endif REGRESS := __placeholder__ EXTRA_CLEAN += $(control_path) spock_compat.bc \ diff --git a/include/spock.h b/include/spock.h index 2d96941f..c9a38557 100644 --- a/include/spock.h +++ b/include/spock.h @@ -28,7 +28,6 @@ #define SPOCK_VERSION_NUM 60000 #define EXTENSION_NAME "spock" -#define SPOCK_SECLABEL_PROVIDER "spock" #define REPLICATION_ORIGIN_ALL "all" diff --git a/include/spock_relcache.h b/include/spock_relcache.h index a9a689de..8bdfa83b 100644 --- a/include/spock_relcache.h +++ b/include/spock_relcache.h @@ -44,12 +44,11 @@ typedef struct SpockRelation Oid idxoid; Relation rel; int *attmap; + bool has_delta_columns; + Oid *delta_apply_functions; /* Additional cache, only valid as long as relation mapping is. */ bool hasTriggers; - - Oid *delta_apply_functions; - bool has_delta_columns; } SpockRelation; extern void spock_relation_cache_update(uint32 remoteid, @@ -69,8 +68,6 @@ extern void spock_relation_cache_reset(void); extern Oid spock_lookup_delta_function(char *fname, Oid typeoid); -extern Oid get_replication_identity(Relation rel); - struct SpockTupleData; #endif /* SPOCK_RELCACHE_H */ diff --git a/patches/15/pg15-015-attoptions.diff b/patches/15/pg15-015-attoptions.diff new file mode 100644 index 00000000..338cc845 --- /dev/null +++ b/patches/15/pg15-015-attoptions.diff @@ -0,0 +1,198 @@ +diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c +index 620602fba2d..8eebc4cde65 100644 +--- a/src/backend/access/common/reloptions.c ++++ b/src/backend/access/common/reloptions.c +@@ -168,6 +168,15 @@ static relopt_bool boolRelOpts[] = + }, + true + }, ++ { ++ { ++ "log_old_value", ++ "Add old value of attribute to WAL for logical decoding", ++ RELOPT_KIND_ATTRIBUTE, ++ ShareUpdateExclusiveLock ++ }, ++ false ++ }, + /* list terminator */ + {{NULL}} + }; +@@ -548,6 +557,19 @@ static relopt_enum enumRelOpts[] = + + static relopt_string stringRelOpts[] = + { ++ { ++ { ++ "delta_apply_function", ++ "Function called to perform delta conflict avoidance", ++ RELOPT_KIND_ATTRIBUTE, ++ ShareUpdateExclusiveLock ++ }, ++ -1, ++ true, ++ NULL, ++ NULL, ++ NULL ++ }, + /* list terminator */ + {{NULL}} + }; +@@ -2085,7 +2107,9 @@ attribute_reloptions(Datum reloptions, bool validate) + { + static const relopt_parse_elt tab[] = { + {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, +- {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)} ++ {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)}, ++ {"log_old_value", RELOPT_TYPE_BOOL, offsetof(AttributeOpts, log_old_value)}, ++ {"delta_apply_function", RELOPT_TYPE_STRING, offsetof(AttributeOpts, delta_apply_function)} + }; + + return (bytea *) build_reloptions(reloptions, validate, +diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c +index ef0e5eeca30..290cea25b12 100644 +--- a/src/backend/access/heap/heapam.c ++++ b/src/backend/access/heap/heapam.c +@@ -67,6 +67,7 @@ + #include "storage/smgr.h" + #include "storage/spin.h" + #include "storage/standby.h" ++#include "utils/attoptcache.h" + #include "utils/datum.h" + #include "utils/inval.h" + #include "utils/lsyscache.h" +@@ -88,6 +89,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, + HeapTuple newtup); + static void check_inplace_rel_lock(HeapTuple oldtup); + #endif ++static Bitmapset *HeapDetermineLogOldColumns(Relation relation); + static Bitmapset *HeapDetermineColumnsInfo(Relation relation, + Bitmapset *interesting_cols, + Bitmapset *external_cols, +@@ -122,6 +124,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); + static int bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate); + static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); + static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_required, ++ Bitmapset *logged_old_attrs, + bool *copy); + + +@@ -2962,7 +2965,7 @@ l1: + * Compute replica identity tuple before entering the critical section so + * we don't PANIC upon a memory allocation failure. + */ +- old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied); ++ old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, NULL, &old_key_copied); + + /* + * If this is the first possibly-multixact-able operation in the current +@@ -3193,6 +3196,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, + Bitmapset *id_attrs; + Bitmapset *interesting_attrs; + Bitmapset *modified_attrs; ++ Bitmapset *logged_old_attrs; + ItemId lp; + HeapTupleData oldtup; + HeapTuple heaptup; +@@ -3355,6 +3359,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, + id_attrs, &oldtup, + newtup, &id_has_external); + ++ if (!IsCatalogRelationOid(relation->rd_id)) ++ logged_old_attrs = HeapDetermineLogOldColumns(relation); ++ else ++ logged_old_attrs = NULL; /* No need to log old values for catalog tables */ ++ + /* + * If we're not updating any "key" column, we can grab a weaker lock type. + * This allows for more concurrency when we are running simultaneously +@@ -3625,6 +3634,7 @@ l2: + bms_free(key_attrs); + bms_free(id_attrs); + bms_free(modified_attrs); ++ bms_free(logged_old_attrs); + bms_free(interesting_attrs); + return result; + } +@@ -3961,6 +3971,7 @@ l2: + old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, + bms_overlap(modified_attrs, id_attrs) || + id_has_external, ++ logged_old_attrs, + &old_key_copied); + + /* NO EREPORT(ERROR) from here till changes are logged */ +@@ -4110,6 +4121,7 @@ l2: + bms_free(key_attrs); + bms_free(id_attrs); + bms_free(modified_attrs); ++ bms_free(logged_old_attrs); + bms_free(interesting_attrs); + + return TM_Ok; +@@ -4282,6 +4294,26 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, + } + } + ++static Bitmapset * ++HeapDetermineLogOldColumns(Relation relation) ++{ ++ int attnum; ++ Bitmapset *logged_cols = NULL; ++ TupleDesc tupdesc = RelationGetDescr(relation); ++ AttributeOpts *aopt; ++ ++ for (attnum = 1; attnum <= tupdesc->natts; attnum++) ++ { ++ aopt = get_attribute_options(relation->rd_id, attnum); ++ if (aopt != NULL && aopt->log_old_value) ++ logged_cols = bms_add_member(logged_cols, ++ attnum - ++ FirstLowInvalidHeapAttributeNumber); ++ } ++ ++ return logged_cols; ++} ++ + /* + * Check which columns are being updated. + * +@@ -9085,6 +9117,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) + */ + static HeapTuple + ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, ++ Bitmapset *logged_old_attrs, + bool *copy) + { + TupleDesc desc = RelationGetDescr(relation); +@@ -9117,13 +9150,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, + } + + /* if the key isn't required and we're only logging the key, we're done */ +- if (!key_required) ++ if (!key_required && logged_old_attrs == NULL) + return NULL; + + /* find out the replica identity columns */ + idattrs = RelationGetIndexAttrBitmap(relation, + INDEX_ATTR_BITMAP_IDENTITY_KEY); + ++ /* merge the columns that are marked LOG_OLD_VALUE */ ++ idattrs = bms_union(idattrs, logged_old_attrs); ++ + /* + * If there's no defined replica identity columns, treat as !key_required. + * (This case should not be reachable from heap_update, since that should +diff --git a/src/include/utils/attoptcache.h b/src/include/utils/attoptcache.h +index ee37af95001..98b48a8fd89 100644 +--- a/src/include/utils/attoptcache.h ++++ b/src/include/utils/attoptcache.h +@@ -21,6 +21,8 @@ typedef struct AttributeOpts + int32 vl_len_; /* varlena header (do not touch directly!) */ + float8 n_distinct; + float8 n_distinct_inherited; ++ bool log_old_value; ++ Oid delta_apply_function; + } AttributeOpts; + + extern AttributeOpts *get_attribute_options(Oid spcid, int attnum); diff --git a/patches/16/pg16-015-attoptions.diff b/patches/16/pg16-015-attoptions.diff new file mode 100644 index 00000000..9d542f87 --- /dev/null +++ b/patches/16/pg16-015-attoptions.diff @@ -0,0 +1,198 @@ +diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c +index 9648a769580..087656983cd 100644 +--- a/src/backend/access/common/reloptions.c ++++ b/src/backend/access/common/reloptions.c +@@ -168,6 +168,15 @@ static relopt_bool boolRelOpts[] = + }, + true + }, ++ { ++ { ++ "log_old_value", ++ "Add old value of attribute to WAL for logical decoding", ++ RELOPT_KIND_ATTRIBUTE, ++ ShareUpdateExclusiveLock ++ }, ++ false ++ }, + /* list terminator */ + {{NULL}} + }; +@@ -548,6 +557,19 @@ static relopt_enum enumRelOpts[] = + + static relopt_string stringRelOpts[] = + { ++ { ++ { ++ "delta_apply_function", ++ "Function called to perform delta conflict avoidance", ++ RELOPT_KIND_ATTRIBUTE, ++ ShareUpdateExclusiveLock ++ }, ++ -1, ++ true, ++ NULL, ++ NULL, ++ NULL ++ }, + /* list terminator */ + {{NULL}} + }; +@@ -2081,7 +2103,9 @@ attribute_reloptions(Datum reloptions, bool validate) + { + static const relopt_parse_elt tab[] = { + {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, +- {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)} ++ {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)}, ++ {"log_old_value", RELOPT_TYPE_BOOL, offsetof(AttributeOpts, log_old_value)}, ++ {"delta_apply_function", RELOPT_TYPE_STRING, offsetof(AttributeOpts, delta_apply_function)} + }; + + return (bytea *) build_reloptions(reloptions, validate, +diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c +index b251653540e..be87929c323 100644 +--- a/src/backend/access/heap/heapam.c ++++ b/src/backend/access/heap/heapam.c +@@ -68,6 +68,7 @@ + #include "storage/smgr.h" + #include "storage/spin.h" + #include "storage/standby.h" ++#include "utils/attoptcache.h" + #include "utils/datum.h" + #include "utils/inval.h" + #include "utils/lsyscache.h" +@@ -89,6 +90,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, + HeapTuple newtup); + static void check_inplace_rel_lock(HeapTuple oldtup); + #endif ++static Bitmapset *HeapDetermineLogOldColumns(Relation relation); + static Bitmapset *HeapDetermineColumnsInfo(Relation relation, + Bitmapset *interesting_cols, + Bitmapset *external_cols, +@@ -126,6 +128,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); + static int bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate); + static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); + static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, ++ Bitmapset *logged_old_attrs, + bool *copy); + + +@@ -2823,7 +2826,7 @@ l1: + * Compute replica identity tuple before entering the critical section so + * we don't PANIC upon a memory allocation failure. + */ +- old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied); ++ old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, NULL, &old_key_copied); + + /* + * If this is the first possibly-multixact-able operation in the current +@@ -3056,6 +3059,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, + Bitmapset *id_attrs; + Bitmapset *interesting_attrs; + Bitmapset *modified_attrs; ++ Bitmapset *logged_old_attrs; + ItemId lp; + HeapTupleData oldtup; + HeapTuple heaptup; +@@ -3225,6 +3229,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, + id_attrs, &oldtup, + newtup, &id_has_external); + ++ if (!IsCatalogRelationOid(relation->rd_id)) ++ logged_old_attrs = HeapDetermineLogOldColumns(relation); ++ else ++ logged_old_attrs = NULL; /* No need to log old values for catalog tables */ ++ + /* + * If we're not updating any "key" column, we can grab a weaker lock type. + * This allows for more concurrency when we are running simultaneously +@@ -3498,6 +3507,7 @@ l2: + bms_free(key_attrs); + bms_free(id_attrs); + bms_free(modified_attrs); ++ bms_free(logged_old_attrs); + bms_free(interesting_attrs); + return result; + } +@@ -3847,6 +3857,7 @@ l2: + old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, + bms_overlap(modified_attrs, id_attrs) || + id_has_external, ++ logged_old_attrs, + &old_key_copied); + + /* NO EREPORT(ERROR) from here till changes are logged */ +@@ -4013,6 +4024,7 @@ l2: + bms_free(key_attrs); + bms_free(id_attrs); + bms_free(modified_attrs); ++ bms_free(logged_old_attrs); + bms_free(interesting_attrs); + + return TM_Ok; +@@ -4185,6 +4197,26 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, + } + } + ++static Bitmapset * ++HeapDetermineLogOldColumns(Relation relation) ++{ ++ int attnum; ++ Bitmapset *logged_cols = NULL; ++ TupleDesc tupdesc = RelationGetDescr(relation); ++ AttributeOpts *aopt; ++ ++ for (attnum = 1; attnum <= tupdesc->natts; attnum++) ++ { ++ aopt = get_attribute_options(relation->rd_id, attnum); ++ if (aopt != NULL && aopt->log_old_value) ++ logged_cols = bms_add_member(logged_cols, ++ attnum - ++ FirstLowInvalidHeapAttributeNumber); ++ } ++ ++ return logged_cols; ++} ++ + /* + * Check which columns are being updated. + * +@@ -9225,6 +9257,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) + */ + static HeapTuple + ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, ++ Bitmapset *logged_old_attrs, + bool *copy) + { + TupleDesc desc = RelationGetDescr(relation); +@@ -9257,13 +9290,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, + } + + /* if the key isn't required and we're only logging the key, we're done */ +- if (!key_required) ++ if (!key_required && logged_old_attrs == NULL) + return NULL; + + /* find out the replica identity columns */ + idattrs = RelationGetIndexAttrBitmap(relation, + INDEX_ATTR_BITMAP_IDENTITY_KEY); + ++ /* merge the columns that are marked LOG_OLD_VALUE */ ++ idattrs = bms_union(idattrs, logged_old_attrs); ++ + /* + * If there's no defined replica identity columns, treat as !key_required. + * (This case should not be reachable from heap_update, since that should +diff --git a/src/include/utils/attoptcache.h b/src/include/utils/attoptcache.h +index e4119b6aa28..6354a981570 100644 +--- a/src/include/utils/attoptcache.h ++++ b/src/include/utils/attoptcache.h +@@ -21,6 +21,8 @@ typedef struct AttributeOpts + int32 vl_len_; /* varlena header (do not touch directly!) */ + float8 n_distinct; + float8 n_distinct_inherited; ++ bool log_old_value; ++ Oid delta_apply_function; + } AttributeOpts; + + extern AttributeOpts *get_attribute_options(Oid attrelid, int attnum); diff --git a/patches/17/pg17-015-attoptions.diff b/patches/17/pg17-015-attoptions.diff new file mode 100644 index 00000000..0fe3209c --- /dev/null +++ b/patches/17/pg17-015-attoptions.diff @@ -0,0 +1,198 @@ +diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c +index c6a2d13be8d..8e93a084ad6 100644 +--- a/src/backend/access/common/reloptions.c ++++ b/src/backend/access/common/reloptions.c +@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] = + }, + true + }, ++ { ++ { ++ "log_old_value", ++ "Add old value of attribute to WAL for logical decoding", ++ RELOPT_KIND_ATTRIBUTE, ++ ShareUpdateExclusiveLock ++ }, ++ false ++ }, + /* list terminator */ + {{NULL}} + }; +@@ -546,6 +555,19 @@ static relopt_enum enumRelOpts[] = + + static relopt_string stringRelOpts[] = + { ++ { ++ { ++ "delta_apply_function", ++ "Function called to perform delta conflict avoidance", ++ RELOPT_KIND_ATTRIBUTE, ++ ShareUpdateExclusiveLock ++ }, ++ -1, ++ true, ++ NULL, ++ NULL, ++ NULL ++ }, + /* list terminator */ + {{NULL}} + }; +@@ -2079,7 +2101,9 @@ attribute_reloptions(Datum reloptions, bool validate) + { + static const relopt_parse_elt tab[] = { + {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, +- {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)} ++ {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)}, ++ {"log_old_value", RELOPT_TYPE_BOOL, offsetof(AttributeOpts, log_old_value)}, ++ {"delta_apply_function", RELOPT_TYPE_STRING, offsetof(AttributeOpts, delta_apply_function)} + }; + + return (bytea *) build_reloptions(reloptions, validate, +diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c +index 1bacea4360e..20f60178c72 100644 +--- a/src/backend/access/heap/heapam.c ++++ b/src/backend/access/heap/heapam.c +@@ -65,6 +65,7 @@ + #include "storage/predicate.h" + #include "storage/procarray.h" + #include "storage/standby.h" ++#include "utils/attoptcache.h" + #include "utils/datum.h" + #include "utils/injection_point.h" + #include "utils/inval.h" +@@ -86,6 +87,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, + HeapTuple newtup); + static void check_inplace_rel_lock(HeapTuple oldtup); + #endif ++static Bitmapset *HeapDetermineLogOldColumns(Relation relation); + static Bitmapset *HeapDetermineColumnsInfo(Relation relation, + Bitmapset *interesting_cols, + Bitmapset *external_cols, +@@ -125,6 +127,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); + static int bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate); + static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); + static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, ++ Bitmapset *logged_old_attrs, + bool *copy); + + +@@ -2980,7 +2983,7 @@ l1: + * Compute replica identity tuple before entering the critical section so + * we don't PANIC upon a memory allocation failure. + */ +- old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied); ++ old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, NULL, &old_key_copied); + + /* + * If this is the first possibly-multixact-able operation in the current +@@ -3213,6 +3216,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, + Bitmapset *id_attrs; + Bitmapset *interesting_attrs; + Bitmapset *modified_attrs; ++ Bitmapset *logged_old_attrs; + ItemId lp; + HeapTupleData oldtup; + HeapTuple heaptup; +@@ -3383,6 +3387,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, + id_attrs, &oldtup, + newtup, &id_has_external); + ++ if (!IsCatalogRelationOid(relation->rd_id)) ++ logged_old_attrs = HeapDetermineLogOldColumns(relation); ++ else ++ logged_old_attrs = NULL; /* No need to log old values for catalog tables */ ++ + /* + * If we're not updating any "key" column, we can grab a weaker lock type. + * This allows for more concurrency when we are running simultaneously +@@ -3656,6 +3665,7 @@ l2: + bms_free(key_attrs); + bms_free(id_attrs); + bms_free(modified_attrs); ++ bms_free(logged_old_attrs); + bms_free(interesting_attrs); + return result; + } +@@ -4005,6 +4015,7 @@ l2: + old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, + bms_overlap(modified_attrs, id_attrs) || + id_has_external, ++ logged_old_attrs, + &old_key_copied); + + /* NO EREPORT(ERROR) from here till changes are logged */ +@@ -4171,6 +4182,7 @@ l2: + bms_free(key_attrs); + bms_free(id_attrs); + bms_free(modified_attrs); ++ bms_free(logged_old_attrs); + bms_free(interesting_attrs); + + return TM_Ok; +@@ -4343,6 +4355,26 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, + } + } + ++static Bitmapset * ++HeapDetermineLogOldColumns(Relation relation) ++{ ++ int attnum; ++ Bitmapset *logged_cols = NULL; ++ TupleDesc tupdesc = RelationGetDescr(relation); ++ AttributeOpts *aopt; ++ ++ for (attnum = 1; attnum <= tupdesc->natts; attnum++) ++ { ++ aopt = get_attribute_options(relation->rd_id, attnum); ++ if (aopt != NULL && aopt->log_old_value) ++ logged_cols = bms_add_member(logged_cols, ++ attnum - ++ FirstLowInvalidHeapAttributeNumber); ++ } ++ ++ return logged_cols; ++} ++ + /* + * Check which columns are being updated. + * +@@ -9207,6 +9239,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) + */ + static HeapTuple + ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, ++ Bitmapset *logged_old_attrs, + bool *copy) + { + TupleDesc desc = RelationGetDescr(relation); +@@ -9239,13 +9272,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, + } + + /* if the key isn't required and we're only logging the key, we're done */ +- if (!key_required) ++ if (!key_required && logged_old_attrs == NULL) + return NULL; + + /* find out the replica identity columns */ + idattrs = RelationGetIndexAttrBitmap(relation, + INDEX_ATTR_BITMAP_IDENTITY_KEY); + ++ /* merge the columns that are marked LOG_OLD_VALUE */ ++ idattrs = bms_union(idattrs, logged_old_attrs); ++ + /* + * If there's no defined replica identity columns, treat as !key_required. + * (This case should not be reachable from heap_update, since that should +diff --git a/src/include/utils/attoptcache.h b/src/include/utils/attoptcache.h +index a1a9bfc0fb9..e9b6dfab474 100644 +--- a/src/include/utils/attoptcache.h ++++ b/src/include/utils/attoptcache.h +@@ -21,6 +21,8 @@ typedef struct AttributeOpts + int32 vl_len_; /* varlena header (do not touch directly!) */ + float8 n_distinct; + float8 n_distinct_inherited; ++ bool log_old_value; ++ Oid delta_apply_function; + } AttributeOpts; + + extern AttributeOpts *get_attribute_options(Oid attrelid, int attnum); diff --git a/patches/18/pg18-015-attoptions.diff b/patches/18/pg18-015-attoptions.diff new file mode 100644 index 00000000..dd42114d --- /dev/null +++ b/patches/18/pg18-015-attoptions.diff @@ -0,0 +1,199 @@ +diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c +index 50747c16396..6cdbbe1d0dd 100644 +--- a/src/backend/access/common/reloptions.c ++++ b/src/backend/access/common/reloptions.c +@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] = + }, + true + }, ++ { ++ { ++ "log_old_value", ++ "Add old value of attribute to WAL for logical decoding", ++ RELOPT_KIND_ATTRIBUTE, ++ ShareUpdateExclusiveLock ++ }, ++ false ++ }, + /* list terminator */ + {{NULL}} + }; +@@ -557,6 +566,19 @@ static relopt_enum enumRelOpts[] = + + static relopt_string stringRelOpts[] = + { ++ { ++ { ++ "delta_apply_function", ++ "Function called to perform delta conflict avoidance", ++ RELOPT_KIND_ATTRIBUTE, ++ ShareUpdateExclusiveLock ++ }, ++ -1, ++ true, ++ NULL, ++ NULL, ++ NULL ++ }, + /* list terminator */ + {{NULL}} + }; +@@ -2106,7 +2128,9 @@ attribute_reloptions(Datum reloptions, bool validate) + { + static const relopt_parse_elt tab[] = { + {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, +- {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)} ++ {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)}, ++ {"log_old_value", RELOPT_TYPE_BOOL, offsetof(AttributeOpts, log_old_value)}, ++ {"delta_apply_function", RELOPT_TYPE_STRING, offsetof(AttributeOpts, delta_apply_function)} + }; + + return (bytea *) build_reloptions(reloptions, validate, +diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c +index 0dcd6ee817e..dcf26167cae 100644 +--- a/src/backend/access/heap/heapam.c ++++ b/src/backend/access/heap/heapam.c +@@ -48,6 +48,7 @@ + #include "storage/lmgr.h" + #include "storage/predicate.h" + #include "storage/procarray.h" ++#include "utils/attoptcache.h" + #include "utils/datum.h" + #include "utils/injection_point.h" + #include "utils/inval.h" +@@ -67,6 +68,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, + HeapTuple newtup); + static void check_inplace_rel_lock(HeapTuple oldtup); + #endif ++static Bitmapset *HeapDetermineLogOldColumns(Relation relation); + static Bitmapset *HeapDetermineColumnsInfo(Relation relation, + Bitmapset *interesting_cols, + Bitmapset *external_cols, +@@ -104,6 +106,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); + static int bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate); + static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); + static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, ++ Bitmapset *logged_old_attrs, + bool *copy); + + +@@ -3016,7 +3019,7 @@ l1: + * Compute replica identity tuple before entering the critical section so + * we don't PANIC upon a memory allocation failure. + */ +- old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied); ++ old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, NULL, &old_key_copied); + + /* + * If this is the first possibly-multixact-able operation in the current +@@ -3249,6 +3252,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, + Bitmapset *id_attrs; + Bitmapset *interesting_attrs; + Bitmapset *modified_attrs; ++ Bitmapset *logged_old_attrs; + ItemId lp; + HeapTupleData oldtup; + HeapTuple heaptup; +@@ -3419,6 +3423,12 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, + id_attrs, &oldtup, + newtup, &id_has_external); + ++ if (!IsCatalogRelationOid(relation->rd_id)) ++ logged_old_attrs = HeapDetermineLogOldColumns(relation); ++ else ++ /* No need to log old values for catalog tables */ ++ logged_old_attrs = NULL; ++ + /* + * If we're not updating any "key" column, we can grab a weaker lock type. + * This allows for more concurrency when we are running simultaneously +@@ -3692,6 +3702,7 @@ l2: + bms_free(key_attrs); + bms_free(id_attrs); + bms_free(modified_attrs); ++ bms_free(logged_old_attrs); + bms_free(interesting_attrs); + return result; + } +@@ -4041,6 +4052,7 @@ l2: + old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, + bms_overlap(modified_attrs, id_attrs) || + id_has_external, ++ logged_old_attrs, + &old_key_copied); + + /* NO EREPORT(ERROR) from here till changes are logged */ +@@ -4207,6 +4219,7 @@ l2: + bms_free(key_attrs); + bms_free(id_attrs); + bms_free(modified_attrs); ++ bms_free(logged_old_attrs); + bms_free(interesting_attrs); + + return TM_Ok; +@@ -4379,6 +4392,26 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, + } + } + ++static Bitmapset * ++HeapDetermineLogOldColumns(Relation relation) ++{ ++ int attnum; ++ Bitmapset *logged_cols = NULL; ++ TupleDesc tupdesc = RelationGetDescr(relation); ++ AttributeOpts *aopt; ++ ++ for (attnum = 1; attnum <= tupdesc->natts; attnum++) ++ { ++ aopt = get_attribute_options(relation->rd_id, attnum); ++ if (aopt != NULL && aopt->log_old_value) ++ logged_cols = bms_add_member(logged_cols, ++ attnum - ++ FirstLowInvalidHeapAttributeNumber); ++ } ++ ++ return logged_cols; ++} ++ + /* + * Check which columns are being updated. + * +@@ -9132,6 +9165,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) + */ + static HeapTuple + ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, ++ Bitmapset *logged_old_attrs, + bool *copy) + { + TupleDesc desc = RelationGetDescr(relation); +@@ -9164,13 +9198,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, + } + + /* if the key isn't required and we're only logging the key, we're done */ +- if (!key_required) ++ if (!key_required && logged_old_attrs == NULL) + return NULL; + + /* find out the replica identity columns */ + idattrs = RelationGetIndexAttrBitmap(relation, + INDEX_ATTR_BITMAP_IDENTITY_KEY); + ++ /* merge the columns that are marked LOG_OLD_VALUE */ ++ idattrs = bms_union(idattrs, logged_old_attrs); ++ + /* + * If there's no defined replica identity columns, treat as !key_required. + * (This case should not be reachable from heap_update, since that should +diff --git a/src/include/utils/attoptcache.h b/src/include/utils/attoptcache.h +index f684a772af5..6c965fede13 100644 +--- a/src/include/utils/attoptcache.h ++++ b/src/include/utils/attoptcache.h +@@ -21,6 +21,8 @@ typedef struct AttributeOpts + int32 vl_len_; /* varlena header (do not touch directly!) */ + float8 n_distinct; + float8 n_distinct_inherited; ++ bool log_old_value; ++ Oid delta_apply_function; + } AttributeOpts; + + extern AttributeOpts *get_attribute_options(Oid attrelid, int attnum); diff --git a/sql/spock--5.0.6--6.0.0-devel.sql b/sql/spock--5.0.6--6.0.0-devel.sql index f0078a09..cd6dd8f6 100644 --- a/sql/spock--5.0.6--6.0.0-devel.sql +++ b/sql/spock--5.0.6--6.0.0-devel.sql @@ -210,128 +210,6 @@ RETURNS void AS 'MODULE_PATHNAME', 'spock_reset_subscription_stats' LANGUAGE C CALLED ON NULL INPUT VOLATILE; --- Set delta_apply security label on specific column -CREATE FUNCTION spock.delta_apply( - rel regclass, - att_name name, - to_drop boolean DEFAULT false -) RETURNS boolean AS $$ -DECLARE - label text; - atttype name; - attdata record; - sqlstring text; - status boolean; - relreplident char (1); - ctypname name; -BEGIN - - /* - * regclass input type guarantees we see this table, no 'not found' check - * is needed. - */ - SELECT c.relreplident FROM pg_class c WHERE oid = rel INTO relreplident; - /* - * Allow only DEFAULT type of replica identity. FULL type means we have - * already requested delta_apply feature on this table. - * Avoid INDEX type because indexes may have different names on the nodes and - * it would be better to stay paranoid than afraid of consequences. - */ - IF (relreplident <> 'd' AND relreplident <> 'f') - THEN - RAISE EXCEPTION 'spock can apply delta_apply feature to the DEFAULT replica identity type only. This table holds "%" idenity', relreplident; - END IF; - - /* - * Find proper delta_apply function for the column type or ERROR - */ - - SELECT t.typname,t.typinput,t.typoutput, a.attnotnull - FROM pg_catalog.pg_attribute a, pg_type t - WHERE a.attrelid = rel AND a.attname = att_name AND (a.atttypid = t.oid) - INTO attdata; - IF NOT FOUND THEN - RAISE EXCEPTION 'column % does not exist in the table %', att_name, rel; - END IF; - - IF (attdata.attnotnull = false) THEN - /* - * TODO: Here is a case where the table has different constraints on nodes. - * Using prepared transactions, we might be sure this operation will finish - * if only each node satisfies the rule. But we need to add support for 2PC - * commit beforehand. - */ - RAISE NOTICE USING - MESSAGE = format('delta_apply feature can not be applied to nullable column %L of the table %I', - att_name, rel), - HINT = 'Set NOT NULL constraint on the column', - ERRCODE = 'object_not_in_prerequisite_state'; - RETURN false; - END IF; - - SELECT typname FROM pg_type WHERE - typname IN ('int2','int4','int8','float4','float8','numeric','money') AND - typinput = attdata.typinput AND typoutput = attdata.typoutput - INTO ctypname; - IF NOT FOUND THEN - RAISE EXCEPTION 'type "%" can not be used in delta_apply conflict resolution', - attdata.typname; - END IF; - - -- - -- Create security label on the column - -- - IF (to_drop = true) THEN - sqlstring := format('SECURITY LABEL FOR spock ON COLUMN %I.%I IS NULL;' , - rel, att_name); - ELSE - sqlstring := format('SECURITY LABEL FOR spock ON COLUMN %I.%I IS %L;' , - rel, att_name, 'spock.delta_apply'); - END IF; - - EXECUTE sqlstring; - - /* - * Auto replication will propagate security label if needed. Just warn if it's - * not - the structure sync pg_dump call would copy security labels, isn't it? - */ - SELECT pg_catalog.current_setting('spock.enable_ddl_replication') INTO status; - IF EXISTS (SELECT 1 FROM spock.local_node) AND status = false THEN - raise WARNING 'delta_apply setting has not been propagated to other spock nodes'; - END IF; - - IF EXISTS (SELECT 1 FROM pg_catalog.pg_seclabel - WHERE objoid = rel AND classoid = 'pg_class'::regclass AND - provider = 'spock') THEN - /* - * Call it each time to trigger relcache invalidation callback that causes - * refresh of the SpockRelation entry and guarantees actual state of the - * delta_apply columns. - */ - EXECUTE format('ALTER TABLE %I REPLICA IDENTITY FULL', rel); - ELSIF EXISTS (SELECT 1 FROM pg_catalog.pg_class c - WHERE c.oid = rel AND c.relreplident = 'f') THEN - /* - * Have removed he last security label. Revert this spock hack change, - * if needed. - */ - EXECUTE format('ALTER TABLE %I REPLICA IDENTITY DEFAULT', rel); - END IF; - - RETURN true; -END; -$$ LANGUAGE plpgsql STRICT VOLATILE; - - --- 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; - 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); diff --git a/sql/spock--6.0.0-devel.sql b/sql/spock--6.0.0-devel.sql index 35cbebe1..d96a1b9f 100644 --- a/sql/spock--6.0.0-devel.sql +++ b/sql/spock--6.0.0-devel.sql @@ -750,33 +750,19 @@ CREATE FUNCTION spock.terminate_active_transactions() RETURNS bool -- Generic delta apply functions for all numeric data types -- ---- CREATE FUNCTION spock.delta_apply(int2, int2, int2) -RETURNS int2 -AS 'MODULE_PATHNAME', 'delta_apply_int2' -LANGUAGE C; +RETURNS int2 LANGUAGE c AS 'MODULE_PATHNAME', 'delta_apply_int2'; CREATE FUNCTION spock.delta_apply(int4, int4, int4) -RETURNS int4 -AS 'MODULE_PATHNAME', 'delta_apply_int4' -LANGUAGE C; +RETURNS int4 LANGUAGE c AS 'MODULE_PATHNAME', 'delta_apply_int4'; CREATE FUNCTION spock.delta_apply(int8, int8, int8) -RETURNS int8 -AS 'MODULE_PATHNAME', 'delta_apply_int8' -LANGUAGE C; +RETURNS int8 LANGUAGE c AS 'MODULE_PATHNAME', 'delta_apply_int8'; CREATE FUNCTION spock.delta_apply(float4, float4, float4) -RETURNS float4 -AS 'MODULE_PATHNAME', 'delta_apply_float4' -LANGUAGE C; +RETURNS float4 LANGUAGE c AS 'MODULE_PATHNAME', 'delta_apply_float4'; CREATE FUNCTION spock.delta_apply(float8, float8, float8) -RETURNS float8 -AS 'MODULE_PATHNAME', 'delta_apply_float8' -LANGUAGE C; +RETURNS float8 LANGUAGE c AS 'MODULE_PATHNAME', 'delta_apply_float8'; CREATE FUNCTION spock.delta_apply(numeric, numeric, numeric) -RETURNS numeric -AS 'MODULE_PATHNAME', 'delta_apply_numeric' -LANGUAGE C; +RETURNS numeric LANGUAGE c AS 'MODULE_PATHNAME', 'delta_apply_numeric'; CREATE FUNCTION spock.delta_apply(money, money, money) -RETURNS money -AS 'MODULE_PATHNAME', 'delta_apply_money' -LANGUAGE C; +RETURNS money LANGUAGE c AS 'MODULE_PATHNAME', 'delta_apply_money'; -- ---- -- Function to control REPAIR mode @@ -860,115 +846,3 @@ CREATE FUNCTION spock.reset_subscription_stats(subid oid DEFAULT NULL) RETURNS void AS 'MODULE_PATHNAME', 'spock_reset_subscription_stats' LANGUAGE C CALLED ON NULL INPUT VOLATILE; - --- Set delta_apply security label on specific column -CREATE FUNCTION spock.delta_apply( - rel regclass, - att_name name, - to_drop boolean DEFAULT false -) RETURNS boolean AS $$ -DECLARE - label text; - atttype name; - attdata record; - sqlstring text; - status boolean; - relreplident char (1); - ctypname name; -BEGIN - - /* - * regclass input type guarantees we see this table, no 'not found' check - * is needed. - */ - SELECT c.relreplident FROM pg_class c WHERE oid = rel INTO relreplident; - /* - * Allow only DEFAULT type of replica identity. FULL type means we have - * already requested delta_apply feature on this table. - * Avoid INDEX type because indexes may have different names on the nodes and - * it would be better to stay paranoid than afraid of consequences. - */ - IF (relreplident <> 'd' AND relreplident <> 'f') - THEN - RAISE EXCEPTION 'spock can apply delta_apply feature to the DEFAULT replica identity type only. This table holds "%" idenity', relreplident; - END IF; - - /* - * Find proper delta_apply function for the column type or ERROR - */ - - SELECT t.typname,t.typinput,t.typoutput, a.attnotnull - FROM pg_catalog.pg_attribute a, pg_type t - WHERE a.attrelid = rel AND a.attname = att_name AND (a.atttypid = t.oid) - INTO attdata; - IF NOT FOUND THEN - RAISE EXCEPTION 'column % does not exist in the table %', att_name, rel; - END IF; - - IF (attdata.attnotnull = false) THEN - /* - * TODO: Here is a case where the table has different constraints on nodes. - * Using prepared transactions, we might be sure this operation will finish - * if only each node satisfies the rule. But we need to add support for 2PC - * commit beforehand. - */ - RAISE NOTICE USING - MESSAGE = format('delta_apply feature can not be applied to nullable column %L of the table %I', - att_name, rel), - HINT = 'Set NOT NULL constraint on the column', - ERRCODE = 'object_not_in_prerequisite_state'; - RETURN false; - END IF; - - SELECT typname FROM pg_type WHERE - typname IN ('int2','int4','int8','float4','float8','numeric','money') AND - typinput = attdata.typinput AND typoutput = attdata.typoutput - INTO ctypname; - IF NOT FOUND THEN - RAISE EXCEPTION 'type "%" can not be used in delta_apply conflict resolution', - attdata.typname; - END IF; - - -- - -- Create security label on the column - -- - IF (to_drop = true) THEN - sqlstring := format('SECURITY LABEL FOR spock ON COLUMN %I.%I IS NULL;' , - rel, att_name); - ELSE - sqlstring := format('SECURITY LABEL FOR spock ON COLUMN %I.%I IS %L;' , - rel, att_name, 'spock.delta_apply'); - END IF; - - EXECUTE sqlstring; - - /* - * Auto replication will propagate security label if needed. Just warn if it's - * not - the structure sync pg_dump call would copy security labels, isn't it? - */ - SELECT pg_catalog.current_setting('spock.enable_ddl_replication') INTO status; - IF EXISTS (SELECT 1 FROM spock.local_node) AND status = false THEN - raise WARNING 'delta_apply setting has not been propagated to other spock nodes'; - END IF; - - IF EXISTS (SELECT 1 FROM pg_catalog.pg_seclabel - WHERE objoid = rel AND classoid = 'pg_class'::regclass AND - provider = 'spock') THEN - /* - * Call it each time to trigger relcache invalidation callback that causes - * refresh of the SpockRelation entry and guarantees actual state of the - * delta_apply columns. - */ - EXECUTE format('ALTER TABLE %I REPLICA IDENTITY FULL', rel); - ELSIF EXISTS (SELECT 1 FROM pg_catalog.pg_class c - WHERE c.oid = rel AND c.relreplident = 'f') THEN - /* - * Have removed he last security label. Revert this spock hack change, - * if needed. - */ - EXECUTE format('ALTER TABLE %I REPLICA IDENTITY DEFAULT', rel); - END IF; - - RETURN true; -END; -$$ LANGUAGE plpgsql STRICT VOLATILE; diff --git a/src/spock.c b/src/spock.c index 8def4d39..cdb980d8 100644 --- a/src/spock.c +++ b/src/spock.c @@ -25,7 +25,6 @@ #include "catalog/pg_type.h" #include "commands/extension.h" -#include "commands/seclabel.h" #include "executor/executor.h" @@ -915,32 +914,6 @@ log_message_filter(ErrorData *edata) } } -/* - * Spock security label hook. - * - * Just to be sure user adds a proper label: only table columns may be applied. - */ -static void -spock_object_relabel(const ObjectAddress *object, const char *seclabel) -{ - Oid extoid; - - extoid = get_extension_oid(EXTENSION_NAME, true); - if (!OidIsValid(extoid)) - elog(ERROR, "spock extension is not created yet"); - - /* - * Check: classId must be pg_class, objectId should an existing table and - * attnum must be more than 0. - */ - if (object->classId != RelationRelationId || object->objectSubId <= 0 || - !SearchSysCacheExists1(RELOID, ObjectIdGetDatum(object->objectId))) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("spock provider does not support labels on %s", - getObjectTypeDescription(object, false)))); -} - /* * Entry point for this module. */ @@ -1277,9 +1250,6 @@ _PG_init(void) prev_emit_log_hook = emit_log_hook; emit_log_hook = log_message_filter; - /* Security label provider hook */ - register_label_provider(SPOCK_SECLABEL_PROVIDER, spock_object_relabel); - #if PG_VERSION_NUM >= 180000 /* Spock replication conflict statistics */ spock_stat_register_conflict_stat(); diff --git a/src/spock_apply.c b/src/spock_apply.c index 6d35b242..8cdc4e5e 100644 --- a/src/spock_apply.c +++ b/src/spock_apply.c @@ -3647,12 +3647,9 @@ spock_execute_sql_command(char *cmdstr, char *role, bool isTopLevel) /* * check if it's a DDL statement. we only do this for * in_spock_replicate_ddl_command - * SECURITY LABEL command is not a DDL, just an utility one. Hence, let - * spock execute this command. */ if (in_spock_replicate_ddl_command && - GetCommandLogLevel(command->stmt) != LOGSTMT_DDL && - !IsA(command->stmt, SecLabelStmt)) + GetCommandLogLevel(command->stmt) != LOGSTMT_DDL) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), diff --git a/src/spock_apply_heap.c b/src/spock_apply_heap.c index 46253d76..6eb833b7 100644 --- a/src/spock_apply_heap.c +++ b/src/spock_apply_heap.c @@ -98,9 +98,11 @@ typedef struct ApplyExecState #define TTS_TUP(slot) (((HeapTupleTableSlot *)slot)->tuple) +#ifndef NO_LOG_OLD_VALUE static void build_delta_tuple(SpockRelation *rel, SpockTupleData *oldtup, SpockTupleData *newtup, SpockTupleData *deltatup, TupleTableSlot *localslot); +#endif static bool physatt_in_attmap(SpockRelation *rel, int attid); /* @@ -511,6 +513,8 @@ physatt_in_attmap(SpockRelation *rel, int attid) } +#ifndef NO_LOG_OLD_VALUE + static void build_delta_tuple(SpockRelation *rel, SpockTupleData *oldtup, SpockTupleData *newtup, @@ -541,33 +545,48 @@ build_delta_tuple(SpockRelation *rel, SpockTupleData *oldtup, } /* - * This shouldn't happen: creating delta_apply column we must check that - * NOT NULL constraint is set on this column or reject. But just to - * survive in case of a bug we complain and send the apply worker to - * exception behavior path way. + * Column is marked LOG_OLD_VALUE=true. We use that as flag to apply + * the delta between the remote old and new instead of the plain new + * value. + * + * To perform the actual delta math we need the functions behind the + * '+' and '-' operators for the data type. + * + * XXX: This is currently hardcoded for the builtin data types we + * support. Ideally we would lookup those operators in the system + * cache, but that isn't straight forward and we get into all sorts of + * trouble when it comes to user defined data types and the search + * path. */ - if (oldtup->nulls[remoteattnum] || newtup->nulls[remoteattnum]) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("delta apply column can't operate NULL values"), - errdetail("attribute %d for remote tuple is %s, and for the local tuple is %s", - remoteattnum + 1, - newtup->nulls[remoteattnum] ? "NULL" : "NOT NULL", - oldtup->nulls[remoteattnum] ? "NULL" : "NOT NULL" - ))); - - loc_value = heap_getattr(TTS_TUP(localslot), remoteattnum + 1, tupdesc, - &loc_isnull); - Assert(!loc_isnull); - - result = OidFunctionCall3Coll(rel->delta_apply_functions[remoteattnum], - InvalidOid, oldtup->values[remoteattnum], - newtup->values[remoteattnum], loc_value); - deltatup->values[remoteattnum] = result; - deltatup->nulls[remoteattnum] = false; - deltatup->changed[remoteattnum] = true; + + if (oldtup->nulls[remoteattnum]) + { + /* + * This is a special case. Columns for delta apply need to be + * marked NOT NULL and LOG_OLD_VALUE=true. During this remote + * UPDATE LOG_OLD_VALUE setting was false. We use this as a flag + * to force plain NEW value application. This is useful in case a + * server ever gets out of sync. + */ + deltatup->values[remoteattnum] = newtup->values[remoteattnum]; + deltatup->nulls[remoteattnum] = false; + deltatup->changed[remoteattnum] = true; + } + else + { + loc_value = heap_getattr(TTS_TUP(localslot), remoteattnum + 1, tupdesc, + &loc_isnull); + + result = OidFunctionCall3Coll(rel->delta_apply_functions[remoteattnum], + InvalidOid, oldtup->values[remoteattnum], + newtup->values[remoteattnum], loc_value); + deltatup->values[remoteattnum] = result; + deltatup->nulls[remoteattnum] = false; + deltatup->changed[remoteattnum] = true; + } } } +#endif /* NO_LOG_OLD_VALUE */ /** diff --git a/src/spock_autoddl.c b/src/spock_autoddl.c index cb4e37b3..17eb1001 100644 --- a/src/spock_autoddl.c +++ b/src/spock_autoddl.c @@ -21,7 +21,7 @@ #include "commands/defrem.h" #include "commands/extension.h" -#include "commands/seclabel.h" + #include "tcop/utility.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -30,7 +30,6 @@ #include "spock_autoddl.h" #include "spock_executor.h" #include "spock_queue.h" -#include "spock_relcache.h" #include "spock_repset.h" #include "spock_node.h" #include "spock_output_plugin.h" @@ -282,8 +281,7 @@ add_ddl_to_repset(Node *parsetree) } if (!OidIsValid(targetrel->rd_replidindex) && - (repset->replicate_update || repset->replicate_delete) && - !OidIsValid(get_replication_identity(targetrel))) + (repset->replicate_update || repset->replicate_delete)) { table_close(targetrel, NoLock); return; @@ -344,9 +342,8 @@ autoddl_can_proceed(Node *parsetree, ProcessUtilityContext context, */ return false; - /* Only process DDL statements and SECURITY LABEL's */ - if (GetCommandLogLevel(parsetree) != LOGSTMT_DDL && - !IsA(parsetree, SecLabelStmt)) + /* Only process DDL statements */ + if (GetCommandLogLevel(parsetree) != LOGSTMT_DDL) return false; /* If DDL replication is disabled, do nothing */ diff --git a/src/spock_executor.c b/src/spock_executor.c index b5d4f51d..ad469abb 100644 --- a/src/spock_executor.c +++ b/src/spock_executor.c @@ -20,18 +20,16 @@ #include "catalog/dependency.h" #include "catalog/index.h" -#include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_authid_d.h" #include "catalog/pg_extension.h" #include "catalog/pg_inherits.h" -#include "catalog/pg_seclabel.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/extension.h" -#include "commands/seclabel.h" + #include "executor/executor.h" #include "nodes/nodeFuncs.h" @@ -191,40 +189,6 @@ spock_ProcessUtility(PlannedStmt *pstmt, const char *queryString, spock_autoddl_process(pstmt, queryString, context, toplevel_stmt); } -/* - * Derived from the core DeleteSecurityLabel routine - */ -static void -DeleteSecurityLabels(const char *provider) -{ - Relation pg_seclabel; - SysScanDesc scan; - HeapTuple htup; - - pg_seclabel = table_open(SecLabelRelationId, RowExclusiveLock); - - scan = systable_beginscan(pg_seclabel, InvalidOid, false, NULL, 0, NULL); - while (HeapTupleIsValid(htup = systable_getnext(scan))) - { - Datum datum; - bool isnull; - char *provider; - - datum = heap_getattr(htup, Anum_pg_seclabel_provider, - RelationGetDescr(pg_seclabel), &isnull); - Assert(!isnull); - provider = TextDatumGetCString(datum); - - if (strcmp(provider, SPOCK_SECLABEL_PROVIDER) != 0) - continue; - - CatalogTupleDelete(pg_seclabel, &htup->t_self); - } - - systable_endscan(scan); - table_close(pg_seclabel, RowExclusiveLock); -} - /* * Handle object drop. * @@ -239,7 +203,6 @@ spock_object_access(ObjectAccessType access, { Oid save_userid = 0; int save_sec_context = 0; - ObjectAddress object; if (next_object_access_hook) (*next_object_access_hook) (access, classId, objectId, subId, arg); @@ -247,6 +210,7 @@ spock_object_access(ObjectAccessType access, if (access == OAT_DROP) { ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg; + ObjectAddress object; DropBehavior behavior; /* No need to check for internal deletions. */ @@ -277,11 +241,7 @@ spock_object_access(ObjectAccessType access, * be handled by Postgres. */ if (dropping_spock_obj) - { - /* Need to drop any security labels created by the extension */ - DeleteSecurityLabels(SPOCK_SECLABEL_PROVIDER); return; - } /* * Check that we have a local node. We need to elevate access because @@ -309,28 +269,6 @@ spock_object_access(ObjectAccessType access, /* Restore previous session privileges */ SetUserIdAndSecContext(save_userid, save_sec_context); } - /* SECURITY LABEL related section (see delta_apply for more details) */ - else if (access == OAT_POST_ALTER && subId > 0) - { - char *label; - - /* - * Something changes in the definition of the column. We have not enough - * data at the moment to check if the column will satisfy delta_apply - * type requirements. So, just warn and drop security label, if exists. - * TODO: the direction of further improvement is discovery of syscache - * or querying the table definition in attempt to identify the new type. - */ - ObjectAddressSubSet(object, classId, objectId, subId); - label = GetSecurityLabel(&object, "spock"); - if (label != NULL) - { - DeleteSecurityLabel(&object); - elog(WARNING, "the alter column statement removes spock security label '%s' on this column", - label); - pfree(label); - } - } } void diff --git a/src/spock_relcache.c b/src/spock_relcache.c index 182957e5..1a7a5988 100644 --- a/src/spock_relcache.c +++ b/src/spock_relcache.c @@ -16,7 +16,7 @@ #include "catalog/namespace.h" #include "catalog/pg_trigger.h" -#include "commands/seclabel.h" + #include "utils/attoptcache.h" #include "utils/builtins.h" #include "utils/catcache.h" @@ -87,6 +87,7 @@ spock_relation_open(uint32 remoteid, LOCKMODE lockmode) { RangeVar *rv = makeNode(RangeVar); int i; + TupleDesc desc; ResultRelInfo *relinfo; rv->schemaname = (char *) entry->nspname; @@ -95,74 +96,48 @@ spock_relation_open(uint32 remoteid, LOCKMODE lockmode) if (unlikely(entry->rel == NULL)) return NULL; - + desc = RelationGetDescr(entry->rel); for (i = 0; i < entry->natts; i++) { - ObjectAddress object; - char *seclabel; - TupleDesc desc; + AttributeOpts *aopt; - desc = RelationGetDescr(entry->rel); entry->attmap[i] = tupdesc_get_att_by_name(desc, entry->attnames[i]); - if (entry->rel->rd_rel->relreplident != REPLICA_IDENTITY_FULL) - continue; - /* - * Read security labels for each attname. For each such an attribute - * choose corresponding delta function. - * - * XXX: What about non-existing columns on remote side? + * If we find attribute options for this column and the + * delta_apply_function is set, lookup the oid for it. */ - ObjectAddressSubSet(object, RelationRelationId, - RelationGetRelid(entry->rel), - entry->attmap[i] + 1); - seclabel = GetSecurityLabel(&object, SPOCK_SECLABEL_PROVIDER); - if (seclabel != NULL) + aopt = get_attribute_options(entry->rel->rd_id, + entry->attmap[i] + 1); + if (aopt != NULL && aopt->delta_apply_function != 0) { - Form_pg_attribute att; - Oid dfunc; + char *fname; + Form_pg_attribute att; + Oid dfunc; att = TupleDescAttr(desc, entry->attmap[i]); - dfunc = spock_lookup_delta_function(seclabel, att->atttypid); + fname = pstrdup(GET_STRING_RELOPTION(aopt, + delta_apply_function)); + dfunc = spock_lookup_delta_function(fname, att->atttypid); + pfree(fname); if (dfunc == InvalidOid) elog(ERROR, "SPOCK: column %s.%s.%s is configured for " - "delta_apply function %s - function not found", + "delta_apply_function %s - function not found", entry->nspname, entry->relname, - entry->attnames[i], seclabel); + entry->attnames[i], + GET_STRING_RELOPTION(aopt, delta_apply_function)); + - entry->delta_apply_functions[entry->attmap[i]] = dfunc; - Assert(entry->delta_apply_functions[entry->attmap[i]] != InvalidOid); entry->has_delta_columns = true; - } - else - { - /* Main case */ - entry->delta_apply_functions[entry->attmap[i]] = InvalidOid; + entry->delta_apply_functions[entry->attmap[i]] = dfunc; } } relinfo = makeNode(ResultRelInfo); InitResultRelInfo(relinfo, entry->rel, 1, NULL, 0); entry->reloid = RelationGetRelid(entry->rel); - if (entry->has_delta_columns) - { - /* - * It looks like a hack — which, in fact, it is. - * We assume that delta_apply may be used for the DEFAULT identity - * only and will be immediately removed after altering the table. - * Also, if an ERROR happens here we will stay with an inconsistent - * value of the relreplident field. But it is just a cache ... - */ - relinfo->ri_RelationDesc->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT; - relinfo->ri_RelationDesc->rd_indexvalid = false; - entry->idxoid = RelationGetReplicaIndex(relinfo->ri_RelationDesc); - Assert(entry->idxoid != InvalidOid); - relinfo->ri_RelationDesc->rd_rel->relreplident = REPLICA_IDENTITY_FULL; - } - else - entry->idxoid = RelationGetReplicaIndex(relinfo->ri_RelationDesc); + entry->idxoid = RelationGetReplicaIndex(relinfo->ri_RelationDesc); /* Cache trigger info. */ entry->hasTriggers = false; @@ -233,7 +208,7 @@ spock_relation_cache_update(uint32 remoteid, char *schemaname, } entry->attmap = palloc(natts * sizeof(int)); entry->has_delta_columns = false; - entry->delta_apply_functions = (Oid *) palloc0(entry->natts * sizeof(Oid)); + entry->delta_apply_functions = palloc0(natts * sizeof(Oid)); MemoryContextSwitchTo(oldcontext); /* XXX Should we validate the relation against local schema here? */ @@ -271,7 +246,7 @@ spock_relation_cache_updater(SpockRemoteRel *remoterel) entry->attnames[i] = pstrdup(remoterel->attnames[i]); entry->attmap = palloc(remoterel->natts * sizeof(int)); entry->has_delta_columns = false; - entry->delta_apply_functions = (Oid *) palloc0(entry->natts * sizeof(Oid)); + entry->delta_apply_functions = palloc0(remoterel->natts * sizeof(Oid)); MemoryContextSwitchTo(oldcontext); /* XXX Should we validate the relation against local schema here? */ @@ -428,44 +403,3 @@ spock_lookup_delta_function(char *fname, Oid typeoid) return funcoid; } - -/* - * Detect if the FULL identity is just covering delta_apply - * feature underpinned by PK. - * This is quite a rare case. Don't afraid overhead. - */ -Oid -get_replication_identity(Relation rel) -{ - TupleDesc tupDesc = RelationGetDescr(rel); - int i = 0; - - if (OidIsValid(rel->rd_replidindex)) - return rel->rd_replidindex; - - if (rel->rd_rel->relreplident != REPLICA_IDENTITY_FULL || - !OidIsValid(rel->rd_pkindex)) - return InvalidOid; - - for (i = 0; i < tupDesc->natts; i++) - { - char *seclabel; - ObjectAddress object; - - ObjectAddressSubSet(object, RelationRelationId, - RelationGetRelid(rel), i + 1); - seclabel = GetSecurityLabel(&object, SPOCK_SECLABEL_PROVIDER); - - if (seclabel != NULL) - { - /* - * Relation has at least on security label on it. Treat it as a - * delta_apply feature. - */ - pfree(seclabel); - return rel->rd_pkindex; - } - } - - return InvalidOid; -} diff --git a/src/spock_repset.c b/src/spock_repset.c index c3539dbf..ae926bde 100644 --- a/src/spock_repset.c +++ b/src/spock_repset.c @@ -45,7 +45,6 @@ #include "spock_dependency.h" #include "spock_node.h" #include "spock_queue.h" -#include "spock_relcache.h" #include "spock_repset.h" #include "spock.h" #include "spock_compat.h" @@ -1131,7 +1130,7 @@ replication_set_add_table(Oid setid, Oid reloid, List *att_list, if (targetrel->rd_indexvalid == 0) RelationGetIndexList(targetrel); - if (!OidIsValid(get_replication_identity(targetrel)) && + if (!OidIsValid(targetrel->rd_replidindex) && (repset->replicate_update || repset->replicate_delete)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), diff --git a/tests/regress/expected/autoddl.out b/tests/regress/expected/autoddl.out index 2005d958..09cc8039 100644 --- a/tests/regress/expected/autoddl.out +++ b/tests/regress/expected/autoddl.out @@ -100,77 +100,9 @@ WARNING: This DDL statement will not be replicated. DROP TABLE test_383 CASCADE; NOTICE: drop cascades to table test_383 membership in replication set default_insert_only INFO: DDL statement replicated. -\c :provider_dsn -\set VERBOSITY terse --- Check propagation of security labels -CREATE TABLE slabel1 (x integer NOT NULL, y text PRIMARY KEY); -INFO: DDL statement replicated. -SELECT spock.delta_apply('slabel1', 'x'); -INFO: DDL statement replicated. -INFO: DDL statement replicated. - delta_apply -------------- - t -(1 row) - -SELECT spock.delta_apply('slabel1', 'y'); -- ERROR -ERROR: type "text" can not be used in delta_apply conflict resolution -SELECT spock.delta_apply('slabel1', 'z'); -- ERROR -ERROR: column z does not exist in the table slabel1 -SELECT spock.delta_apply('slabel1', 'x'); -- repeating call do nothing -INFO: DDL statement replicated. -INFO: DDL statement replicated. - delta_apply -------------- - t -(1 row) - -SELECT objname, label FROM pg_seclabels; - objname | label ------------+------------------- - slabel1.x | spock.delta_apply -(1 row) - --- Wait for the apply worker to process the security label before checking. -SELECT spock.sync_event() AS sync_lsn \gset -\c :subscriber_dsn -CALL spock.wait_for_sync_event(NULL, 'test_provider', :'sync_lsn', 60); - result --------- - t -(1 row) - -SELECT objname, label FROM pg_seclabels; - objname | label ------------+------------------- - slabel1.x | spock.delta_apply -(1 row) - -\c :provider_dsn -SELECT spock.delta_apply('slabel1', 'x', true); -INFO: DDL statement replicated. -INFO: DDL statement replicated. - delta_apply -------------- - t -(1 row) - --- Wait for the apply worker to process the removal before checking. -SELECT spock.sync_event() AS sync_lsn \gset \c :subscriber_dsn -CALL spock.wait_for_sync_event(NULL, 'test_provider', :'sync_lsn', 60); - result --------- - t -(1 row) - -SELECT objname, label FROM pg_seclabels; - objname | label ----------+------- -(0 rows) - -\c :provider_dsn -- Reset the configuration to the default value +\c :provider_dsn ALTER SYSTEM SET spock.enable_ddl_replication = 'off'; WARNING: This DDL statement will not be replicated. ALTER SYSTEM SET spock.include_ddl_repset = 'off'; diff --git a/tests/regress/expected/basic.out b/tests/regress/expected/basic.out index 78ef5101..642df00c 100644 --- a/tests/regress/expected/basic.out +++ b/tests/regress/expected/basic.out @@ -211,96 +211,3 @@ CREATE FUNCTION call_fn(creds text) RETURNS void AS $$ $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; SELECT call_fn(:fakecreds); ERROR: dsn "dbname=regression passwordXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -DROP FUNCTION call_fn; --- --- Basic SECURITY LABEL tests. Fix limits of acceptable behavior. --- Remember, these tests still check intra-node behaviour. --- --- A label creation checks -CREATE TABLE slabel (x integer NOT NULL, y text PRIMARY KEY); -CREATE TABLE slabel_ri (x integer NOT NULL, y text, z text); -CREATE UNIQUE INDEX slabel_ri_idx ON slabel_ri(x); -ALTER TABLE slabel_ri REPLICA IDENTITY USING INDEX slabel_ri_idx; -SELECT spock.delta_apply('slabel', 'x'); -WARNING: delta_apply setting has not been propagated to other spock nodes - delta_apply -------------- - t -(1 row) - -SELECT spock.delta_apply('slabel', 'y'); -- ERROR -ERROR: type "text" can not be used in delta_apply conflict resolution -SELECT spock.delta_apply('slabel', 'z'); -- ERROR -ERROR: column z does not exist in the table slabel -SELECT spock.delta_apply('slabel', 'x'); -- repeating call do nothing -WARNING: delta_apply setting has not been propagated to other spock nodes - delta_apply -------------- - t -(1 row) - -SELECT spock.delta_apply('slabel_ri', 'x'); -- ERROR -ERROR: spock can apply delta_apply feature to the DEFAULT replica identity type only. This table holds "i" idenity -SELECT objname, label FROM pg_seclabels; - objname | label -----------+------------------- - slabel.x | spock.delta_apply -(1 row) - -DROP TABLE slabel_ri CASCADE; --- Short round trip to check that subscriber has no security labels -\c :subscriber_dsn -SELECT objname, label FROM pg_seclabels; - objname | label ----------+------- -(0 rows) - -\c :provider_dsn --- Label drop checks -SELECT spock.delta_apply('slabel', 'x', true); -WARNING: delta_apply setting has not been propagated to other spock nodes - delta_apply -------------- - t -(1 row) - -SELECT spock.delta_apply('slabel', 'y', true); -ERROR: type "text" can not be used in delta_apply conflict resolution -SELECT spock.delta_apply('slabel', 'z', true); -ERROR: column z does not exist in the table slabel -SELECT objname, label FROM pg_seclabels; - objname | label ----------+------- -(0 rows) - --- Dependencies -SELECT spock.delta_apply('slabel', 'x', false); -WARNING: delta_apply setting has not been propagated to other spock nodes - delta_apply -------------- - t -(1 row) - -ALTER TABLE slabel ALTER COLUMN x TYPE text; -- just warn -WARNING: the alter column statement removes spock security label 'spock.delta_apply' on this column -SELECT objname, label FROM pg_seclabels; - objname | label ----------+------- -(0 rows) - -ALTER TABLE slabel DROP COLUMN x; -ALTER TABLE slabel ADD COLUMN x numeric NOT NULL; -SELECT spock.delta_apply('slabel', 'x', false); -WARNING: delta_apply setting has not been propagated to other spock nodes - delta_apply -------------- - t -(1 row) - -ALTER TABLE slabel DROP COLUMN x; -SELECT objname, label FROM pg_seclabels; - objname | label ----------+------- -(0 rows) - -DROP TABLE slabel; diff --git a/tests/regress/expected/infofuncs.out b/tests/regress/expected/infofuncs.out index b2813db0..02b33bfc 100644 --- a/tests/regress/expected/infofuncs.out +++ b/tests/regress/expected/infofuncs.out @@ -20,24 +20,4 @@ WHERE extname = 'spock'; t (1 row) --- Check that security label is cleaned up on the extension drop -CREATE TABLE slabel (x money NOT NULL, y text PRIMARY KEY); -SELECT spock.delta_apply('slabel', 'x', false); - delta_apply -------------- - t -(1 row) - -SELECT objname, label FROM pg_seclabels; - objname | label -----------+------------------- - slabel.x | spock.delta_apply -(1 row) - DROP EXTENSION spock; -SELECT objname, label FROM pg_seclabels; - objname | label ----------+------- -(0 rows) - -DROP TABLE slabel; diff --git a/tests/regress/sql/autoddl.sql b/tests/regress/sql/autoddl.sql index d426ca88..e6e18fcb 100644 --- a/tests/regress/sql/autoddl.sql +++ b/tests/regress/sql/autoddl.sql @@ -65,33 +65,10 @@ CREATE INDEX test_383_y_idx ON test_383 (a); CLUSTER test_383 USING test_383_y_idx; -- Should not be replicated DROP TABLE test_383 CASCADE; -\c :provider_dsn -\set VERBOSITY terse --- Check propagation of security labels -CREATE TABLE slabel1 (x integer NOT NULL, y text PRIMARY KEY); - -SELECT spock.delta_apply('slabel1', 'x'); -SELECT spock.delta_apply('slabel1', 'y'); -- ERROR -SELECT spock.delta_apply('slabel1', 'z'); -- ERROR -SELECT spock.delta_apply('slabel1', 'x'); -- repeating call do nothing -SELECT objname, label FROM pg_seclabels; - --- Wait for the apply worker to process the security label before checking. -SELECT spock.sync_event() AS sync_lsn \gset -\c :subscriber_dsn -CALL spock.wait_for_sync_event(NULL, 'test_provider', :'sync_lsn', 60); -SELECT objname, label FROM pg_seclabels; -\c :provider_dsn - -SELECT spock.delta_apply('slabel1', 'x', true); --- Wait for the apply worker to process the removal before checking. -SELECT spock.sync_event() AS sync_lsn \gset \c :subscriber_dsn -CALL spock.wait_for_sync_event(NULL, 'test_provider', :'sync_lsn', 60); -SELECT objname, label FROM pg_seclabels; -\c :provider_dsn -- Reset the configuration to the default value +\c :provider_dsn ALTER SYSTEM SET spock.enable_ddl_replication = 'off'; ALTER SYSTEM SET spock.include_ddl_repset = 'off'; ALTER SYSTEM SET spock.allow_ddl_from_functions = 'off'; diff --git a/tests/regress/sql/basic.sql b/tests/regress/sql/basic.sql index 3bdc63a1..9bf4900d 100644 --- a/tests/regress/sql/basic.sql +++ b/tests/regress/sql/basic.sql @@ -110,46 +110,3 @@ CREATE FUNCTION call_fn(creds text) RETURNS void AS $$ $$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; SELECT call_fn(:fakecreds); -DROP FUNCTION call_fn; - --- --- Basic SECURITY LABEL tests. Fix limits of acceptable behavior. --- Remember, these tests still check intra-node behaviour. --- - --- A label creation checks -CREATE TABLE slabel (x integer NOT NULL, y text PRIMARY KEY); -CREATE TABLE slabel_ri (x integer NOT NULL, y text, z text); -CREATE UNIQUE INDEX slabel_ri_idx ON slabel_ri(x); -ALTER TABLE slabel_ri REPLICA IDENTITY USING INDEX slabel_ri_idx; - -SELECT spock.delta_apply('slabel', 'x'); -SELECT spock.delta_apply('slabel', 'y'); -- ERROR -SELECT spock.delta_apply('slabel', 'z'); -- ERROR -SELECT spock.delta_apply('slabel', 'x'); -- repeating call do nothing -SELECT spock.delta_apply('slabel_ri', 'x'); -- ERROR -SELECT objname, label FROM pg_seclabels; -DROP TABLE slabel_ri CASCADE; - --- Short round trip to check that subscriber has no security labels -\c :subscriber_dsn -SELECT objname, label FROM pg_seclabels; -\c :provider_dsn - --- Label drop checks -SELECT spock.delta_apply('slabel', 'x', true); -SELECT spock.delta_apply('slabel', 'y', true); -SELECT spock.delta_apply('slabel', 'z', true); -SELECT objname, label FROM pg_seclabels; - --- Dependencies -SELECT spock.delta_apply('slabel', 'x', false); -ALTER TABLE slabel ALTER COLUMN x TYPE text; -- just warn -SELECT objname, label FROM pg_seclabels; -ALTER TABLE slabel DROP COLUMN x; -ALTER TABLE slabel ADD COLUMN x numeric NOT NULL; -SELECT spock.delta_apply('slabel', 'x', false); -ALTER TABLE slabel DROP COLUMN x; -SELECT objname, label FROM pg_seclabels; - -DROP TABLE slabel; diff --git a/tests/regress/sql/infofuncs.sql b/tests/regress/sql/infofuncs.sql index f21daf66..ee7536ac 100644 --- a/tests/regress/sql/infofuncs.sql +++ b/tests/regress/sql/infofuncs.sql @@ -9,12 +9,4 @@ SELECT spock.spock_version() = extversion FROM pg_extension WHERE extname = 'spock'; --- Check that security label is cleaned up on the extension drop -CREATE TABLE slabel (x money NOT NULL, y text PRIMARY KEY); -SELECT spock.delta_apply('slabel', 'x', false); -SELECT objname, label FROM pg_seclabels; - DROP EXTENSION spock; - -SELECT objname, label FROM pg_seclabels; -DROP TABLE slabel; diff --git a/tests/tap/schedule b/tests/tap/schedule index a7894dea..7699c126 100644 --- a/tests/tap/schedule +++ b/tests/tap/schedule @@ -34,7 +34,6 @@ test: 014_pgdump_restore_conflict # break # fi # done -test: 012_delta_apply test: 013_exception_handling # test: 014_rolling_upgrade test: 015_skip_lsn diff --git a/tests/tap/t/012_delta_apply.pl b/tests/tap/t/012_delta_apply.pl deleted file mode 100644 index 49213e2e..00000000 --- a/tests/tap/t/012_delta_apply.pl +++ /dev/null @@ -1,107 +0,0 @@ -use strict; -use warnings; -use Test::More tests => 14; -use IPC::Run; -use lib '.'; -use lib 't'; -use SpockTest qw(create_cluster destroy_cluster system_or_bail get_test_config cross_wire psql_or_bail scalar_query); - -my ($result1, $result2, $result3, $lsn1, $lsn2); - -create_cluster(3, 'Create initial 3-node Spock test cluster'); -cross_wire(3, ['n1', 'n2', 'n3'], 'Cross-wire nodes'); -# Get cluster configuration -my $config = get_test_config(); -my $node_count = $config->{node_count}; -my $node_ports = $config->{node_ports}; -my $host = $config->{host}; -my $dbname = $config->{db_name}; -my $db_user = $config->{db_user}; -my $db_password = $config->{db_password}; -my $pg_bin = $config->{pg_bin}; - -psql_or_bail(1, qq( - CREATE TABLE t1 (id integer PRIMARY KEY, x integer NOT NULL, y integer); - INSERT INTO t1 (id, x) VALUES (1,42); -)); - -psql_or_bail(1, 'SELECT spock.wait_slot_confirm_lsn(NULL, NULL)'); -psql_or_bail(2, 'SELECT spock.wait_slot_confirm_lsn(NULL, NULL)'); -psql_or_bail(3, 'SELECT spock.wait_slot_confirm_lsn(NULL, NULL)'); - -psql_or_bail(1, "SELECT spock.delta_apply('t1', 'x');"); -#psql_or_bail(2, "SELECT spock.delta_apply('t1', 'x');"); -#psql_or_bail(3, "SELECT spock.delta_apply('t1', 'x');"); - -psql_or_bail(3, q( -CREATE PROCEDURE counter_change(relname name, attname name, - value integer, cycles integer) -AS $$ -DECLARE - i integer := 0; -BEGIN - WHILE i < cycles LOOP - EXECUTE format('UPDATE %I SET %s = %s + %L;', relname, attname, attname, value); - -- raise WARNING '[%] iteration % from %', value, i, cycles; - COMMIT; - i := i + 1; - - END LOOP; - raise NOTICE '[%] FINISH: iteration % from %', value, i, cycles; -END; -$$ LANGUAGE plpgsql;)); - -psql_or_bail(1, 'SELECT spock.wait_slot_confirm_lsn(NULL, NULL)'); -psql_or_bail(2, 'SELECT spock.wait_slot_confirm_lsn(NULL, NULL)'); -psql_or_bail(3, 'SELECT spock.wait_slot_confirm_lsn(NULL, NULL)'); - -$result1 = scalar_query(1,"SELECT * FROM pg_catalog.pg_seclabels"); -$result2 = scalar_query(2,"SELECT * FROM pg_catalog.pg_seclabels"); -$result3 = scalar_query(3,"SELECT * FROM pg_catalog.pg_seclabels"); -print STDERR "DEBUGGING. Spock nodes IDs: \n$result1 $result2 $result3\n"; - -my ($pgbench_stdout1, $pgbench_stderr1) = ('', ''); -my $phandle1 = IPC::Run::start( - [ - 'psql', - '-c', "CALL counter_change('t1', 'x', -1, 10000)", - '-h', $host, '-p', $node_ports->[1], '-U', $db_user, $dbname - ], - '>' => \$pgbench_stdout1, - '2>' => \$pgbench_stderr1); -#$phandle1->pump(); - -psql_or_bail(1, "CALL counter_change('t1', 'x', +1, 10001)"); - -$phandle1->finish; -is($phandle1->full_result(0), 0, "alternative run successfull"); - -print STDERR "##### output of psql #####\n"; -print STDERR "$pgbench_stdout1"; -print STDERR "$pgbench_stderr1"; -print STDERR "##### end of output #####\n"; - -# Wait for sync ... -$lsn1 = scalar_query(1, "SELECT spock.sync_event()"); -$lsn2 = scalar_query(2, "SELECT spock.sync_event()"); -psql_or_bail(1, "CALL spock.wait_for_sync_event(true, 'n2', '$lsn2'::pg_lsn, 600)"); -psql_or_bail(2, "CALL spock.wait_for_sync_event(true, 'n1', '$lsn1'::pg_lsn, 600)"); -psql_or_bail(3, "CALL spock.wait_for_sync_event(true, 'n1', '$lsn1'::pg_lsn, 600)"); -psql_or_bail(3, "CALL spock.wait_for_sync_event(true, 'n2', '$lsn2'::pg_lsn, 600)"); - -$result1 = scalar_query(1, "SELECT x FROM t1"); -$result2 = scalar_query(2, "SELECT x FROM t1"); -$result3 = scalar_query(3, "SELECT x FROM t1"); - -ok($result1 eq '43', "Data on the node N1 has correct value"); -ok($result1 eq $result3, "Equality of the data on N1 and N3 is confirmed"); -ok($result2 eq $result3, "Equality of the data on N2 and N3 is confirmed"); -print STDERR "DEBUGGING. Results: $result1 | $result2 | $result3\n"; - -# Remove delta_apply from the column x and set it on the column y -psql_or_bail(1, "SELECT spock.delta_apply('t1', 'x', true)"); -$result1 = scalar_query(1, "SELECT spock.delta_apply('t1', 'y')"); # ERROR -print STDERR "DEBUGGING. Result: $result1\n"; -ok($result1 eq 'f', "delta_apply can't be used with nullable columns"); -# Cleanup will be handled by SpockTest.pm END block -# No need for done_testing() when using a test plan