Thread
-
[PATCH 2/2] Add the "dummy_table_am" test module
Julien Tachoires <julien@tachoires.me> — 2025-03-01T19:50:13Z
This test module is in charge of testing table AM reloptions. It's very similar to what we do in dummy_index_am as we have to exercise the exact same kind of feature. --- src/test/modules/Makefile | 1 + src/test/modules/dummy_table_am/Makefile | 20 + src/test/modules/dummy_table_am/README | 14 + .../dummy_table_am/dummy_table_am--1.0.sql | 13 + .../modules/dummy_table_am/dummy_table_am.c | 581 ++++++++++++++++++ .../dummy_table_am/dummy_table_am.control | 5 + .../dummy_table_am/expected/reloptions.out | 181 ++++++ src/test/modules/dummy_table_am/meson.build | 33 + .../modules/dummy_table_am/sql/reloptions.sql | 99 +++ src/test/modules/meson.build | 1 + 10 files changed, 948 insertions(+) create mode 100644 src/test/modules/dummy_table_am/Makefile create mode 100644 src/test/modules/dummy_table_am/README create mode 100644 src/test/modules/dummy_table_am/dummy_table_am--1.0.sql create mode 100644 src/test/modules/dummy_table_am/dummy_table_am.c create mode 100644 src/test/modules/dummy_table_am/dummy_table_am.control create mode 100644 src/test/modules/dummy_table_am/expected/reloptions.out create mode 100644 src/test/modules/dummy_table_am/meson.build create mode 100644 src/test/modules/dummy_table_am/sql/reloptions.sql diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 4e4be3fa511..8fe2a2904d6 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -9,6 +9,7 @@ SUBDIRS = \ commit_ts \ delay_execution \ dummy_index_am \ + dummy_table_am \ dummy_seclabel \ libpq_pipeline \ oauth_validator \ diff --git a/src/test/modules/dummy_table_am/Makefile b/src/test/modules/dummy_table_am/Makefile new file mode 100644 index 00000000000..94837dff392 --- /dev/null +++ b/src/test/modules/dummy_table_am/Makefile @@ -0,0 +1,20 @@ +# src/test/modules/dummy_table_am/Makefile + +MODULES = dummy_table_am + +EXTENSION = dummy_table_am +DATA = dummy_table_am--1.0.sql +PGFILEDESC = "dummy_table_am - table access method template" + +REGRESS = reloptions + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/dummy_table_am +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/dummy_table_am/README b/src/test/modules/dummy_table_am/README new file mode 100644 index 00000000000..50cf08ee3b1 --- /dev/null +++ b/src/test/modules/dummy_table_am/README @@ -0,0 +1,14 @@ +Dummy Table AM +============== + +Dummy table AM is a module for testing any facility usable by a table +access method, whose code is kept a maximum simple. + +This includes tests for all relation option types: +- boolean +- enum +- integer +- real +- strings (with and without NULL as default) + +It also includes tests related to unrecognized options. diff --git a/src/test/modules/dummy_table_am/dummy_table_am--1.0.sql b/src/test/modules/dummy_table_am/dummy_table_am--1.0.sql new file mode 100644 index 00000000000..12ad3ad174b --- /dev/null +++ b/src/test/modules/dummy_table_am/dummy_table_am--1.0.sql @@ -0,0 +1,13 @@ +/* src/test/modules/dummy_table_am/dummy_table_am--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION dummy_table_am" to load this file. \quit + +CREATE FUNCTION dummy_table_am_handler(internal) +RETURNS table_am_handler +AS 'MODULE_PATHNAME' +LANGUAGE C; + +-- Access method +CREATE ACCESS METHOD dummy_table_am TYPE TABLE HANDLER dummy_table_am_handler; +COMMENT ON ACCESS METHOD dummy_table_am IS 'Dummy Table Access Method'; diff --git a/src/test/modules/dummy_table_am/dummy_table_am.c b/src/test/modules/dummy_table_am/dummy_table_am.c new file mode 100644 index 00000000000..bc9beba195a --- /dev/null +++ b/src/test/modules/dummy_table_am/dummy_table_am.c @@ -0,0 +1,581 @@ +/*------------------------------------------------------------------------- + * + * dummy_table_am.c + * Table AM templae main file + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/test/modules/dummy_table_am/dummy_table_am.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "fmgr.h" +#include "miscadmin.h" + +#include "access/hio.h" +#include "access/relscan.h" +#include "access/reloptions.h" +#include "access/tableam.h" +#include "access/sdir.h" +#include "access/skey.h" +#include "executor/tuptable.h" +#include "utils/relcache.h" +#include "utils/snapshot.h" + + +PG_MODULE_MAGIC; + +/* Base structures for scans */ +typedef struct DummyScanDescData +{ + TableScanDescData rs_base; /* AM independent part of the descriptor */ + + /* Add more fields here as needed by the AM. */ +} DummyScanDescData; +typedef struct DummyScanDescData *DummyScanDesc; + +/* parse table for fillRelOptions */ +static relopt_parse_elt dt_relopt_tab[7]; + +/* Kind of relation options for dummy index */ +static relopt_kind dt_relopt_kind; + +typedef enum DummyAmEnum +{ + DUMMY_AM_ENUM_ONE, + DUMMY_AM_ENUM_TWO, +} DummyAmEnum; + +/* Dummy table options */ +typedef struct DummyTableOptions +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int option_int; + double option_real; + bool option_bool; + DummyAmEnum option_enum; + int option_string_val_offset; + int option_string_null_offset; + int fillfactor; +} DummyTableOptions; + +static relopt_enum_elt_def dummyAmEnumValues[] = +{ + {"one", DUMMY_AM_ENUM_ONE}, + {"two", DUMMY_AM_ENUM_TWO}, + {(const char *) NULL} /* list terminator */ +}; + +/* ------------------------------------------------------------------------ + * Dummy Access Method Interface + * ------------------------------------------------------------------------ + */ + +static const TupleTableSlotOps * +dummy_slot_callbacks(Relation relation) +{ + return &TTSOpsMinimalTuple; +} + +static TableScanDesc +dummy_scan_begin(Relation relation, Snapshot snapshot, int nkeys, ScanKey key, + ParallelTableScanDesc parallel_scan, uint32 flags) +{ + DummyScanDesc scan; + + scan = (DummyScanDesc) palloc(sizeof(DummyScanDescData)); + + scan->rs_base.rs_rd = relation; + scan->rs_base.rs_snapshot = snapshot; + scan->rs_base.rs_nkeys = nkeys; + scan->rs_base.rs_flags = flags; + scan->rs_base.rs_parallel = parallel_scan; + + return (TableScanDesc) scan; +} + +static void +dummy_scan_end(TableScanDesc sscan) +{ + DummyScanDesc scan = (DummyScanDesc) sscan; + + pfree(scan); + + return; +} + +static void +dummy_scan_rescan(TableScanDesc sscan, ScanKey key, bool set_params, + bool allow_strat, bool allow_sync, bool allow_pagemode) +{ + return; +} + +static bool +dummy_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, + TupleTableSlot *slot) +{ + return true; +} + +static void +dummy_scan_set_tidrange(TableScanDesc sscan, ItemPointer mintid, + ItemPointer maxtid) +{ + return; +} + +static bool +dummy_scan_getnextslot_tidrange(TableScanDesc sscan, ScanDirection direction, + TupleTableSlot *slot) +{ + return true; +} + +static Size +dummy_parallelscan_estimate(Relation rel) +{ + return 0; +} + +static Size +dummy_parallelscan_initialize(Relation rel, ParallelTableScanDesc pscan) +{ + return 0; +} + +static void +dummy_parallelscan_reinitialize(Relation rel, ParallelTableScanDesc pscan) +{ + return; +} + +static IndexFetchTableData * +dummy_index_fetch_begin(Relation rel) +{ + return NULL; +} + +static void +dummy_index_fetch_reset(IndexFetchTableData *scan) +{ + return; +} + +static void +dummy_index_fetch_end(IndexFetchTableData *scan) +{ + return; +} + +static bool +dummy_index_fetch_tuple(struct IndexFetchTableData *scan, ItemPointer tid, + Snapshot snapshot, TupleTableSlot *slot, + bool *call_again, bool *all_dead) +{ + return true; +} + +static void +dummy_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid, + int options, BulkInsertStateData *bistate) +{ + DummyTableOptions *relopts; + + relopts = (DummyTableOptions *) relation->rd_options; + + elog(NOTICE, "option_int=%d, option_real=%f, option_bool=%d, option_enum=%d", + relopts->option_int, relopts->option_real, relopts->option_bool, relopts->option_enum); + + return; +} + +static void +dummy_tuple_insert_speculative(Relation relation, TupleTableSlot *slot, + CommandId cid, int options, + BulkInsertStateData *bistate, uint32 specToken) +{ + return; +} + +static void +dummy_tuple_complete_speculative(Relation relation, TupleTableSlot *slot, + uint32 specToken, bool succeeded) +{ + return; +} + +static void +dummy_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, + CommandId cid, int options, BulkInsertStateData *bistate) +{ + return; +} + +static TM_Result +dummy_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, + Snapshot snapshot, Snapshot crosscheck, bool wait, + TM_FailureData *tmfd, bool changingPart) +{ + return TM_Ok; +} + +static TM_Result +dummy_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, + CommandId cid, Snapshot snapshot, Snapshot crosscheck, + bool wait, TM_FailureData *tmfd, + LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes) +{ + return TM_Ok; +} + +static TM_Result +dummy_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot, + TupleTableSlot *slot, CommandId cid, LockTupleMode mode, + LockWaitPolicy wait_policy, uint8 flags, + TM_FailureData *tmfd) +{ + return TM_Ok; +} + +static bool +dummy_fetch_row_version(Relation relation, ItemPointer tid, + Snapshot snapshot, TupleTableSlot *slot) +{ + return false; +} + +static void +dummy_get_latest_tid(TableScanDesc sscan, ItemPointer tid) +{ + return; +} + +static bool +dummy_tuple_tid_valid(TableScanDesc scan, ItemPointer tid) +{ + return false; +} + +static bool +dummy_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, + Snapshot snapshot) +{ + return false; +} + +static TransactionId +dummy_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate) +{ + return InvalidTransactionId; +} + +static void +dummy_relation_set_new_filelocator(Relation rel, + const RelFileLocator *newrlocator, + char persistence, + TransactionId *freezeXid, + MultiXactId *minmulti) +{ + return; +} + +static void +dummy_relation_nontransactional_truncate(Relation rel) +{ + return; +} + +static void +dummy_relation_copy_data(Relation rel, const RelFileLocator *newrlocator) +{ + return; +} + +static void +dummy_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, + Relation OldIndex, bool use_sort, + TransactionId OldestXmin, + TransactionId *xid_cutoff, + MultiXactId *multi_cutoff, + double *num_tuples, + double *tups_vacuumed, + double *tups_recently_dead) +{ + return; +} + +static void +dummy_relation_vacuum(Relation rel, struct VacuumParams *params, + BufferAccessStrategy bstrategy) +{ + return; +} + +static bool +dummy_scan_analyze_next_block(TableScanDesc scan, ReadStream *stream) +{ + return false; +} + +static bool +dummy_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin, + double *liverows, double *deadrows, + TupleTableSlot *slot) +{ + return false; +} + +static double +dummy_index_build_range_scan(Relation heapRelation, + Relation indexRelation, + struct IndexInfo *indexInfo, + bool allow_sync, + bool anyvisible, + bool progress, + BlockNumber start_blockno, + BlockNumber numblocks, + IndexBuildCallback callback, + void *callback_state, + TableScanDesc scan) +{ + return 0; +} + +static void +dummy_index_validate_scan(Relation heapRelation, + Relation indexRelation, + struct IndexInfo *indexInfo, + Snapshot snapshot, + struct ValidateIndexState *state) +{ + return; +} + +static uint64 +dummy_relation_size(Relation rel, ForkNumber forkNumber) +{ + return 0; +} + +static bool +dummy_relation_needs_toast_table(Relation rel) +{ + return false; +} + +static Oid +dummy_relation_toast_am(Relation rel) +{ + return InvalidOid; +} + +static void +dummy_relation_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize, + int32 sliceoffset, int32 slicelength, + struct varlena *result) +{ + return; +} + +static void +dummy_relation_estimate_size(Relation rel, int32 *attr_widths, + BlockNumber *pages, double *tuples, + double *allvisfrac) +{ + return; +} + +static bool +dummy_scan_bitmap_next_tuple(TableScanDesc scan, TupleTableSlot *slot, + bool *recheck, uint64 *lossy_pages, + uint64 *exact_pages) +{ + return false; +} + +static bool +dummy_scan_sample_next_block(TableScanDesc scan, struct SampleScanState *scanstate) +{ + return false; +} + +static bool +dummy_scan_sample_next_tuple(TableScanDesc scan, struct SampleScanState *scanstate, + TupleTableSlot *slot) +{ + return false; +} + +static bytea * +dummy_relation_options(char relkind, Datum reloptions, bool validate) +{ + return (bytea *) build_reloptions(reloptions, validate, + dt_relopt_kind, + sizeof(DummyTableOptions), + dt_relopt_tab, lengthof(dt_relopt_tab)); +} + +/* + * Validation function for string relation options. + */ +static void +validate_string_option(const char *value) +{ + ereport(NOTICE, + (errmsg("new option value for string parameter %s", + value ? value : "NULL"))); +} + +/* + * This function creates a full set of relation option types, + * with various patterns. + */ +static void +create_reloptions_table(void) +{ + dt_relopt_kind = add_reloption_kind(); + + add_int_reloption(dt_relopt_kind, "option_int", + "Integer option for dummy_table_am", + 10, -10, 100, AccessExclusiveLock); + dt_relopt_tab[0].optname = "option_int"; + dt_relopt_tab[0].opttype = RELOPT_TYPE_INT; + dt_relopt_tab[0].offset = offsetof(DummyTableOptions, option_int); + + add_real_reloption(dt_relopt_kind, "option_real", + "Real option for dummy_table_am", + 3.1415, -10, 100, AccessExclusiveLock); + dt_relopt_tab[1].optname = "option_real"; + dt_relopt_tab[1].opttype = RELOPT_TYPE_REAL; + dt_relopt_tab[1].offset = offsetof(DummyTableOptions, option_real); + + add_bool_reloption(dt_relopt_kind, "option_bool", + "Boolean option for dummy_table_am", + true, AccessExclusiveLock); + dt_relopt_tab[2].optname = "option_bool"; + dt_relopt_tab[2].opttype = RELOPT_TYPE_BOOL; + dt_relopt_tab[2].offset = offsetof(DummyTableOptions, option_bool); + + add_enum_reloption(dt_relopt_kind, "option_enum", + "Enum option for dummy_table_am", + dummyAmEnumValues, + DUMMY_AM_ENUM_ONE, + "Valid values are \"one\" and \"two\".", + AccessExclusiveLock); + dt_relopt_tab[3].optname = "option_enum"; + dt_relopt_tab[3].opttype = RELOPT_TYPE_ENUM; + dt_relopt_tab[3].offset = offsetof(DummyTableOptions, option_enum); + + add_string_reloption(dt_relopt_kind, "option_string_val", + "String option for dummy_table_am with non-NULL default", + "DefaultValue", &validate_string_option, + AccessExclusiveLock); + dt_relopt_tab[4].optname = "option_string_val"; + dt_relopt_tab[4].opttype = RELOPT_TYPE_STRING; + dt_relopt_tab[4].offset = offsetof(DummyTableOptions, + option_string_val_offset); + + /* + * String option for dummy_table_am with NULL default, and without + * description. + */ + add_string_reloption(dt_relopt_kind, "option_string_null", + NULL, /* description */ + NULL, &validate_string_option, + AccessExclusiveLock); + dt_relopt_tab[5].optname = "option_string_null"; + dt_relopt_tab[5].opttype = RELOPT_TYPE_STRING; + dt_relopt_tab[5].offset = offsetof(DummyTableOptions, + option_string_null_offset); + + /* + * fillfactor will be used to check reloption conversion when changing + * table access method between heap AM and dummy_table_am. + */ + add_int_reloption(dt_relopt_kind, "fillfactor", + "Fillfactor option for dummy_table_am", + 10, 0, 90, AccessExclusiveLock); + dt_relopt_tab[6].optname = "fillfactor"; + dt_relopt_tab[6].opttype = RELOPT_TYPE_INT; + dt_relopt_tab[6].offset = offsetof(DummyTableOptions, fillfactor); +} + + +/* + * Table Access Method API + */ +static const TableAmRoutine dummy_table_am_methods = { + .type = T_TableAmRoutine, + + .slot_callbacks = dummy_slot_callbacks, + .scan_begin = dummy_scan_begin, + .scan_end = dummy_scan_end, + .scan_rescan = dummy_scan_rescan, + .scan_getnextslot = dummy_scan_getnextslot, + + .scan_set_tidrange = dummy_scan_set_tidrange, + .scan_getnextslot_tidrange = dummy_scan_getnextslot_tidrange, + + .parallelscan_estimate = dummy_parallelscan_estimate, + .parallelscan_initialize = dummy_parallelscan_initialize, + .parallelscan_reinitialize = dummy_parallelscan_reinitialize, + + .index_fetch_begin = dummy_index_fetch_begin, + .index_fetch_reset = dummy_index_fetch_reset, + .index_fetch_end = dummy_index_fetch_end, + .index_fetch_tuple = dummy_index_fetch_tuple, + + .tuple_insert = dummy_tuple_insert, + .tuple_insert_speculative = dummy_tuple_insert_speculative, + .tuple_complete_speculative = dummy_tuple_complete_speculative, + .multi_insert = dummy_multi_insert, + .tuple_delete = dummy_tuple_delete, + .tuple_update = dummy_tuple_update, + .tuple_lock = dummy_tuple_lock, + + .tuple_fetch_row_version = dummy_fetch_row_version, + .tuple_get_latest_tid = dummy_get_latest_tid, + .tuple_tid_valid = dummy_tuple_tid_valid, + .tuple_satisfies_snapshot = dummy_tuple_satisfies_snapshot, + .index_delete_tuples = dummy_index_delete_tuples, + + .relation_set_new_filelocator = dummy_relation_set_new_filelocator, + .relation_nontransactional_truncate = dummy_relation_nontransactional_truncate, + .relation_copy_data = dummy_relation_copy_data, + .relation_copy_for_cluster = dummy_relation_copy_for_cluster, + .relation_vacuum = dummy_relation_vacuum, + .scan_analyze_next_block = dummy_scan_analyze_next_block, + .scan_analyze_next_tuple = dummy_scan_analyze_next_tuple, + .index_build_range_scan = dummy_index_build_range_scan, + .index_validate_scan = dummy_index_validate_scan, + + .relation_size = dummy_relation_size, + .relation_needs_toast_table = dummy_relation_needs_toast_table, + .relation_toast_am = dummy_relation_toast_am, + .relation_fetch_toast_slice = dummy_relation_fetch_toast_slice, + .relation_estimate_size = dummy_relation_estimate_size, + .relation_options = dummy_relation_options, + + .scan_bitmap_next_tuple = dummy_scan_bitmap_next_tuple, + .scan_sample_next_block = dummy_scan_sample_next_block, + .scan_sample_next_tuple = dummy_scan_sample_next_tuple +}; + +PG_FUNCTION_INFO_V1(dummy_table_am_handler); + +Datum +dummy_table_am_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&dummy_table_am_methods); +} + +void +_PG_init(void) +{ + create_reloptions_table(); +} diff --git a/src/test/modules/dummy_table_am/dummy_table_am.control b/src/test/modules/dummy_table_am/dummy_table_am.control new file mode 100644 index 00000000000..08f2f868d49 --- /dev/null +++ b/src/test/modules/dummy_table_am/dummy_table_am.control @@ -0,0 +1,5 @@ +# dummy_table_am extension +comment = 'dummy_table_am - table access method template' +default_version = '1.0' +module_pathname = '$libdir/dummy_table_am' +relocatable = true diff --git a/src/test/modules/dummy_table_am/expected/reloptions.out b/src/test/modules/dummy_table_am/expected/reloptions.out new file mode 100644 index 00000000000..0b947500ead --- /dev/null +++ b/src/test/modules/dummy_table_am/expected/reloptions.out @@ -0,0 +1,181 @@ +-- Tests for relation options +CREATE EXTENSION dummy_table_am; +CREATE TABLE dummy_test_tab (i int4) USING dummy_table_am; +-- Silence validation checks for strings +SET client_min_messages TO 'warning'; +-- Test with default values. +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + unnest +-------- +(0 rows) + +DROP TABLE dummy_test_tab; +-- Test with full set of options. +-- Allow validation checks for strings +SET client_min_messages TO 'notice'; +CREATE TABLE dummy_test_tab (i int4) + USING dummy_table_am WITH ( + option_bool = false, + option_int = 5, + option_real = 3.1, + option_enum = 'two', + option_string_val = NULL, + option_string_null = 'val'); +NOTICE: new option value for string parameter null +NOTICE: new option value for string parameter val +-- Silence again validation checks for strings until the end of the test. +SET client_min_messages TO 'warning'; +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + unnest +------------------------ + option_bool=false + option_int=5 + option_real=3.1 + option_enum=two + option_string_val=null + option_string_null=val +(6 rows) + +-- ALTER TABLE .. SET +ALTER TABLE dummy_test_tab SET (option_int = 10); +ALTER TABLE dummy_test_tab SET (option_bool = true); +ALTER TABLE dummy_test_tab SET (option_real = 3.2); +ALTER TABLE dummy_test_tab SET (option_string_val = 'val2'); +ALTER TABLE dummy_test_tab SET (option_string_null = NULL); +ALTER TABLE dummy_test_tab SET (option_enum = 'one'); +ALTER TABLE dummy_test_tab SET (option_enum = 'three'); +ERROR: invalid value for enum option "option_enum": three +DETAIL: Valid values are "one" and "two". +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + unnest +------------------------- + option_int=10 + option_bool=true + option_real=3.2 + option_string_val=val2 + option_string_null=null + option_enum=one +(6 rows) + +-- ALTER TABLE .. RESET +ALTER TABLE dummy_test_tab RESET (option_int); +ALTER TABLE dummy_test_tab RESET (option_bool); +ALTER TABLE dummy_test_tab RESET (option_real); +ALTER TABLE dummy_test_tab RESET (option_enum); +ALTER TABLE dummy_test_tab RESET (option_string_val); +ALTER TABLE dummy_test_tab RESET (option_string_null); +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + unnest +-------- +(0 rows) + +-- Cross-type checks for reloption values +-- Integer +ALTER TABLE dummy_test_tab SET (option_int = 3.3); -- ok +ALTER TABLE dummy_test_tab SET (option_int = true); -- error +ERROR: invalid value for integer option "option_int": true +ALTER TABLE dummy_test_tab SET (option_int = 'val3'); -- error +ERROR: invalid value for integer option "option_int": val3 +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + unnest +---------------- + option_int=3.3 +(1 row) + +ALTER TABLE dummy_test_tab RESET (option_int); +-- Boolean +ALTER TABLE dummy_test_tab SET (option_bool = 4); -- error +ERROR: invalid value for boolean option "option_bool": 4 +ALTER TABLE dummy_test_tab SET (option_bool = 1); -- ok, as true +ALTER TABLE dummy_test_tab SET (option_bool = 3.4); -- error +ERROR: invalid value for boolean option "option_bool": 3.4 +ALTER TABLE dummy_test_tab SET (option_bool = 'val4'); -- error +ERROR: invalid value for boolean option "option_bool": val4 +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + unnest +--------------- + option_bool=1 +(1 row) + +ALTER TABLE dummy_test_tab RESET (option_bool); +-- Float +ALTER TABLE dummy_test_tab SET (option_real = 4); -- ok +ALTER TABLE dummy_test_tab SET (option_real = true); -- error +ERROR: invalid value for floating point option "option_real": true +ALTER TABLE dummy_test_tab SET (option_real = 'val5'); -- error +ERROR: invalid value for floating point option "option_real": val5 +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + unnest +--------------- + option_real=4 +(1 row) + +ALTER TABLE dummy_test_tab RESET (option_real); +-- Enum +ALTER TABLE dummy_test_tab SET (option_enum = 'one'); -- ok +ALTER TABLE dummy_test_tab SET (option_enum = 0); -- error +ERROR: invalid value for enum option "option_enum": 0 +DETAIL: Valid values are "one" and "two". +ALTER TABLE dummy_test_tab SET (option_enum = true); -- error +ERROR: invalid value for enum option "option_enum": true +DETAIL: Valid values are "one" and "two". +ALTER TABLE dummy_test_tab SET (option_enum = 'three'); -- error +ERROR: invalid value for enum option "option_enum": three +DETAIL: Valid values are "one" and "two". +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + unnest +----------------- + option_enum=one +(1 row) + +ALTER TABLE dummy_test_tab RESET (option_enum); +-- String +ALTER TABLE dummy_test_tab SET (option_string_val = 4); -- ok +ALTER TABLE dummy_test_tab SET (option_string_val = 3.5); -- ok +ALTER TABLE dummy_test_tab SET (option_string_val = true); -- ok, as "true" +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + unnest +------------------------ + option_string_val=true +(1 row) + +ALTER TABLE dummy_test_tab RESET (option_string_val); +DROP TABLE dummy_test_tab; +-- ALTER TABLE SET ACCESS METHOD OPTIONS +CREATE TABLE heap_tab (i INT4) WITH (fillfactor=100, toast_tuple_target=1000); +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'heap_tab'; + unnest +------------------------- + fillfactor=100 + toast_tuple_target=1000 +(2 rows) + +-- error: fillfactor is out of bounds: maximum value from the new table am is 90 +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am; +ERROR: value 100 out of bounds for option "fillfactor" +DETAIL: Valid values are between "0" and "90". +-- error: toast_tuple_target does not exist in the new table AM +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (SET fillfactor '50'); +ERROR: unrecognized parameter "toast_tuple_target" +-- error: adding is not possible when the parameter is already defined in source reloptions +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (ADD fillfactor '50'); +ERROR: option "fillfactor" provided more than once +-- error: the specified option we want to drop does not exist +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (DROP does_not_exist); +ERROR: option "does_not_exist" not found +-- error: adding unrecognized parameter +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (SET fillfactor '50', DROP toast_tuple_target, ADD unrecognized 'foo'); +ERROR: unrecognized parameter "unrecognized" +-- ok +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (DROP fillfactor, DROP toast_tuple_target, option_int '1', option_bool 'true', option_real '0.001', option_enum 'one', option_string_val 'hello'); +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'heap_tab'; + unnest +------------------------- + option_int=1 + option_bool=true + option_real=0.001 + option_enum=one + option_string_val=hello +(5 rows) + +DROP TABLE heap_tab; diff --git a/src/test/modules/dummy_table_am/meson.build b/src/test/modules/dummy_table_am/meson.build new file mode 100644 index 00000000000..6b197b15ffa --- /dev/null +++ b/src/test/modules/dummy_table_am/meson.build @@ -0,0 +1,33 @@ +# Copyright (c) 2022-2025, PostgreSQL Global Development Group + +dummy_table_am_sources = files( + 'dummy_table_am.c', +) + +if host_system == 'windows' + dummy_table_am_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'dummy_table_am', + '--FILEDESC', 'dummy_table_am - table access method template',]) +endif + +dummy_table_am = shared_module('dummy_table_am', + dummy_table_am_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += dummy_table_am + +test_install_data += files( + 'dummy_table_am.control', + 'dummy_table_am--1.0.sql', +) + +tests += { + 'name': 'dummy_table_am', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'reloptions', + ], + }, +} diff --git a/src/test/modules/dummy_table_am/sql/reloptions.sql b/src/test/modules/dummy_table_am/sql/reloptions.sql new file mode 100644 index 00000000000..47fb4862c6c --- /dev/null +++ b/src/test/modules/dummy_table_am/sql/reloptions.sql @@ -0,0 +1,99 @@ +-- Tests for relation options +CREATE EXTENSION dummy_table_am; + +CREATE TABLE dummy_test_tab (i int4) USING dummy_table_am; + +-- Silence validation checks for strings +SET client_min_messages TO 'warning'; + +-- Test with default values. +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; +DROP TABLE dummy_test_tab; + +-- Test with full set of options. +-- Allow validation checks for strings +SET client_min_messages TO 'notice'; +CREATE TABLE dummy_test_tab (i int4) + USING dummy_table_am WITH ( + option_bool = false, + option_int = 5, + option_real = 3.1, + option_enum = 'two', + option_string_val = NULL, + option_string_null = 'val'); +-- Silence again validation checks for strings until the end of the test. +SET client_min_messages TO 'warning'; +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + +-- ALTER TABLE .. SET +ALTER TABLE dummy_test_tab SET (option_int = 10); +ALTER TABLE dummy_test_tab SET (option_bool = true); +ALTER TABLE dummy_test_tab SET (option_real = 3.2); +ALTER TABLE dummy_test_tab SET (option_string_val = 'val2'); +ALTER TABLE dummy_test_tab SET (option_string_null = NULL); +ALTER TABLE dummy_test_tab SET (option_enum = 'one'); +ALTER TABLE dummy_test_tab SET (option_enum = 'three'); +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + +-- ALTER TABLE .. RESET +ALTER TABLE dummy_test_tab RESET (option_int); +ALTER TABLE dummy_test_tab RESET (option_bool); +ALTER TABLE dummy_test_tab RESET (option_real); +ALTER TABLE dummy_test_tab RESET (option_enum); +ALTER TABLE dummy_test_tab RESET (option_string_val); +ALTER TABLE dummy_test_tab RESET (option_string_null); +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; + +-- Cross-type checks for reloption values +-- Integer +ALTER TABLE dummy_test_tab SET (option_int = 3.3); -- ok +ALTER TABLE dummy_test_tab SET (option_int = true); -- error +ALTER TABLE dummy_test_tab SET (option_int = 'val3'); -- error +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; +ALTER TABLE dummy_test_tab RESET (option_int); +-- Boolean +ALTER TABLE dummy_test_tab SET (option_bool = 4); -- error +ALTER TABLE dummy_test_tab SET (option_bool = 1); -- ok, as true +ALTER TABLE dummy_test_tab SET (option_bool = 3.4); -- error +ALTER TABLE dummy_test_tab SET (option_bool = 'val4'); -- error +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; +ALTER TABLE dummy_test_tab RESET (option_bool); +-- Float +ALTER TABLE dummy_test_tab SET (option_real = 4); -- ok +ALTER TABLE dummy_test_tab SET (option_real = true); -- error +ALTER TABLE dummy_test_tab SET (option_real = 'val5'); -- error +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; +ALTER TABLE dummy_test_tab RESET (option_real); +-- Enum +ALTER TABLE dummy_test_tab SET (option_enum = 'one'); -- ok +ALTER TABLE dummy_test_tab SET (option_enum = 0); -- error +ALTER TABLE dummy_test_tab SET (option_enum = true); -- error +ALTER TABLE dummy_test_tab SET (option_enum = 'three'); -- error +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; +ALTER TABLE dummy_test_tab RESET (option_enum); +-- String +ALTER TABLE dummy_test_tab SET (option_string_val = 4); -- ok +ALTER TABLE dummy_test_tab SET (option_string_val = 3.5); -- ok +ALTER TABLE dummy_test_tab SET (option_string_val = true); -- ok, as "true" +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_tab'; +ALTER TABLE dummy_test_tab RESET (option_string_val); + +DROP TABLE dummy_test_tab; + +-- ALTER TABLE SET ACCESS METHOD OPTIONS +CREATE TABLE heap_tab (i INT4) WITH (fillfactor=100, toast_tuple_target=1000); +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'heap_tab'; +-- error: fillfactor is out of bounds: maximum value from the new table am is 90 +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am; +-- error: toast_tuple_target does not exist in the new table AM +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (SET fillfactor '50'); +-- error: adding is not possible when the parameter is already defined in source reloptions +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (ADD fillfactor '50'); +-- error: the specified option we want to drop does not exist +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (DROP does_not_exist); +-- error: adding unrecognized parameter +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (SET fillfactor '50', DROP toast_tuple_target, ADD unrecognized 'foo'); +-- ok +ALTER TABLE heap_tab SET ACCESS METHOD dummy_table_am OPTIONS (DROP fillfactor, DROP toast_tuple_target, option_int '1', option_bool 'true', option_real '0.001', option_enum 'one', option_string_val 'hello'); +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'heap_tab'; +DROP TABLE heap_tab; diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 2b057451473..28398254df7 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -4,6 +4,7 @@ subdir('brin') subdir('commit_ts') subdir('delay_execution') subdir('dummy_index_am') +subdir('dummy_table_am') subdir('dummy_seclabel') subdir('gin') subdir('injection_points') -- 2.39.5 --jr4jupeetksdtvhn--