[PATCH] src/test/modules/dummy_index -- way to test reloptions from inside of access method
Hi!
This patch introduce a dummy_index access method module, that does not do any
indexing at all, but allow to test reloptions from inside of access method
extension.
This patch is part of my bigger work on reloptions refactoring.
It came from
/messages/by-id/20190220060832.GI15532@paquier.xyz
thread where I suggested to add a "enum" reloption type, and we came to
conclusion that we need to test how this new option works for access method
created from extension (it does not work in the same way as in-core access
methods) . But we can't add this option to bloom index, so we need an index
extension that can be freely used for tests.
So I created src/test/modules/dummy_index, it does no real indexing, but it
has all types of reloptions that can be set (reloption_int, reloption_real,
reloption_bool, reloption_string and reloption_string2). It also has set of
boolean GUC variables that enables test output concerning certain reloption:
(do_test_reloption_int, do_test_reloption_real, do_test_reloption_bool and
do_test_reloption_string and do_test_reloption_string2) also set
do_test_reloptions to true to get any output at all.
Dummy index will print this output when index is created, and when record is
inserted (this needed to check if your ALTER TABLE did well)
Then you just use normal regression tests: turns on test output, sets some
reloption and check test output, that it properly reaches the access method
internals.
While writing this module I kept in mind the idea that this module can be also
used for other am-related tests, so I separated the code into two parts:
dummy_index.c has only code related to implementation of an empty access
method, and all code related to reloptions tests were stored into
direloptions.c. So in future somebody can add di[what_ever_he_wants].c whith
his own tests code, add necessary calls to dummy_index.c, create some GUC
variables, and has his own feature tested.
So I kindly ask you to review and commit this module, so I would be able to
continue my work on reloptions refactoring...
Thanks!
Attachments:
dummy_index_v1.difftext/x-patch; charset=UTF-8; name=dummy_index_v1.diffDownload
diff --git a/.gitignore b/.gitignore
index 794e35b..37331c2 100644
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 19d60a5..aa34320 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -7,6 +7,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = \
brin \
commit_ts \
+ dummy_index \
dummy_seclabel \
snapshot_too_old \
test_bloomfilter \
diff --git a/src/test/modules/dummy_index/.gitignore b/src/test/modules/dummy_index/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/dummy_index/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/dummy_index/Makefile b/src/test/modules/dummy_index/Makefile
new file mode 100644
index 0000000..95c1fcc
--- /dev/null
+++ b/src/test/modules/dummy_index/Makefile
@@ -0,0 +1,21 @@
+# contrib/bloom/Makefile
+
+MODULE_big = dummy_index
+OBJS = dummy_index.o direloptions.o $(WIN32RES)
+
+EXTENSION = dummy_index
+DATA = dummy_index--1.0.sql
+PGFILEDESC = "dummy index access method - needed for all kinds of tests"
+
+REGRESS = reloptions
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_index
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_index/README b/src/test/modules/dummy_index/README
new file mode 100644
index 0000000..b704112
--- /dev/null
+++ b/src/test/modules/dummy_index/README
@@ -0,0 +1,34 @@
+DUMMY INDEX
+
+Dummy index, an index module for testing am-related internal calls that can't be
+properly tested inside production indexes, but better to be tested inside actual
+access method module.
+
+Guidelines:
+1. Keep this index as simple as as possible. It should not do any real indexing
+(unless you need it for your tests), if you need it make it as simple as
+possible;
+2. Keep all code related to feature you test inside id[name_of_a_feature].c
+file. dummy_index.c file should contain code that is needed for everyone, and
+make calls to feature-related code;
+3. Code related to your feature tests should be enabled and disabled by GUC
+variables. Create as many boolean GUC variables as you need and set them on so
+your test will know when it is time to do test output. Thus output from
+different features tests are not interfered;
+4. Add section to this README file that describes the general idea of what your
+tests do.
+
+RELOPTIONS TESTS
+
+Here we check that values of reloptions properly reaches am-internals, and that
+all reloption-related code works as expected. Several GUC variables are defined
+to control test's output. do_test_reloptions turns on and off any
+reloptions-related output. You will also get some test output in the case when
+no relation options is set at all. do_test_reloption_int,
+do_test_reloption_real, do_test_reloption_bool and do_test_reloption_string
+allows to enable test output for int, real, boolean and string options.
+do_test_reloption_string2 enables test output for another string option. We need
+second string option, because a) string reloption implementation is a bit
+tricky, and it is better to check that second value works as well; b) better to
+check both cases: string option with validation function and without one, so we
+need to string options.
diff --git a/src/test/modules/dummy_index/direloptions.c b/src/test/modules/dummy_index/direloptions.c
new file mode 100644
index 0000000..174a61d
--- /dev/null
+++ b/src/test/modules/dummy_index/direloptions.c
@@ -0,0 +1,214 @@
+/*-------------------------------------------------------------------------
+ *
+ * direloptions.c
+ * Functions and variables needed for reloptions tests.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/dummy_index/direloptions.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "utils/rel.h"
+#include "access/reloptions.h"
+#include "utils/guc.h"
+
+#include "dummy_index.h"
+
+
+/* parse table for fillRelOptions */
+relopt_parse_elt di_relopt_tab[5];
+
+
+/* Kind of relation options for dummy index */
+relopt_kind di_relopt_kind;
+
+/* GUC variables */
+static bool do_test_reloptions = false;
+static bool do_test_reloption_int = false;
+static bool do_test_reloption_real = false;
+static bool do_test_reloption_bool = false;
+static bool do_test_reloption_string = false;
+static bool do_test_reloption_string2 = false;
+
+/*
+ * This function creates reloptions that would be used in reloptions tests
+ */
+void
+create_reloptions_table()
+{
+ di_relopt_kind = add_reloption_kind();
+
+ add_int_reloption(di_relopt_kind, "int_option",
+ "Int option used for relotions test purposes",
+ 10, -10, 100);
+ di_relopt_tab[0].optname = "int_option";
+ di_relopt_tab[0].opttype = RELOPT_TYPE_INT;
+ di_relopt_tab[0].offset = offsetof(DummyIndexOptions, int_option);
+
+ add_real_reloption(di_relopt_kind, "real_option",
+ "Real option used for relotions test purposes",
+ 3.1415, -10, 100);
+ di_relopt_tab[1].optname = "real_option";
+ di_relopt_tab[1].opttype = RELOPT_TYPE_REAL;
+ di_relopt_tab[1].offset = offsetof(DummyIndexOptions, real_option);
+
+
+ add_bool_reloption(di_relopt_kind, "bool_option",
+ "Boolean option used for relotions test purposes",
+ true);
+ di_relopt_tab[2].optname = "bool_option";
+ di_relopt_tab[2].opttype = RELOPT_TYPE_BOOL;
+ di_relopt_tab[2].offset = offsetof(DummyIndexOptions, bool_option);
+
+ add_string_reloption(di_relopt_kind, "string_option",
+ "String option used for relotions test purposes",
+ "DefaultValue", &validate_string_option);
+ di_relopt_tab[3].optname = "string_option";
+ di_relopt_tab[3].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[3].offset = offsetof(DummyIndexOptions, string_option_offset);
+
+ add_string_reloption(di_relopt_kind, "string_option2",
+ "Seconf string option used for relotions test purposes",
+ "SecondDefaultValue", &validate_string_option);
+ di_relopt_tab[4].optname = "string_option2";
+ di_relopt_tab[4].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[4].offset = offsetof(DummyIndexOptions, string_option2_offset);
+
+}
+
+/*
+ * This function creates GUC variables that allows to turn on and off test
+ * output for different relopntions
+ */
+void
+create_reloptions_test_GUC()
+{
+ DefineCustomBoolVariable("dummy_index.do_test_reloptions",
+ "Set to true if you are going to test reloptions.",
+ NULL,
+ &do_test_reloptions,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_int",
+ "Set to true if you are going to test int reloption.",
+ NULL,
+ &do_test_reloption_int,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_real",
+ "Set to true if you are going to test real reloption.",
+ NULL,
+ &do_test_reloption_real,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_bool",
+ "Set to true if you are going to test boolean reloption.",
+ NULL,
+ &do_test_reloption_bool,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_string",
+ "Set to true if you are going to test string reloption.",
+ NULL,
+ &do_test_reloption_string,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_string2",
+ "Set to true if you are going to test second string reloption.",
+ NULL,
+ &do_test_reloption_string2,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
+/*
+ * Function prints output for reloptions tests if GUC variables switches are
+ * properly set on.
+ */
+void
+print_reloptions_test_output(Relation index)
+{
+ if (do_test_reloptions)
+ {
+ if(!index->rd_options)
+ {
+ elog(WARNING, "No reloptions is set, default values will be chosen in module runtime");
+ } else
+ {
+ DummyIndexOptions * opt;
+ opt = (DummyIndexOptions *) index->rd_options;
+ if (do_test_reloption_int)
+ {
+ elog(WARNING, "int_option = %d", opt->int_option);
+ }
+ if (do_test_reloption_real)
+ {
+ elog(WARNING, "real_option = %f", opt->real_option);
+ }
+ if (do_test_reloption_bool)
+ {
+ elog(WARNING, "bool_option = %d", opt->bool_option);
+ }
+ if (do_test_reloption_string)
+ {
+ char * str;
+ str = (char *) opt + opt->string_option_offset;
+ elog(WARNING, "string_option = '%s'", str);
+ }
+ if (do_test_reloption_string2)
+ {
+ char * str;
+ str = (char *) opt + opt->string_option2_offset;
+ elog(WARNING, "string_option2 = '%s'", str);
+ }
+ }
+ }
+}
+
+/*
+ * Validation function for string_option reloption
+ */
+void
+validate_string_option(const char *value)
+{
+ if (do_test_reloptions && do_test_reloption_string)
+ {
+ if (value)
+ elog(WARNING,"Validating string option '%s'", value);
+ else
+ elog(WARNING,"Validating string option with NULL value");
+ }
+ if (! value || *value == 'I')
+ elog(ERROR, "This seems to be invalid value. Please set valid value");
+}
diff --git a/src/test/modules/dummy_index/dummy_index--1.0.sql b/src/test/modules/dummy_index/dummy_index--1.0.sql
new file mode 100644
index 0000000..27bee5e
--- /dev/null
+++ b/src/test/modules/dummy_index/dummy_index--1.0.sql
@@ -0,0 +1,20 @@
+/* src/test/modules/dummy_index/dummy_index--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_index" to load this file. \quit
+
+CREATE FUNCTION dihandler(internal)
+RETURNS index_am_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+-- Access method
+CREATE ACCESS METHOD dummy_index TYPE INDEX HANDLER dihandler;
+COMMENT ON ACCESS METHOD dummy_index IS 'dummy index is access method for test purposes';
+
+-- Opclasses
+
+CREATE OPERATOR CLASS int4_ops
+DEFAULT FOR TYPE int4 USING dummy_index AS
+ OPERATOR 1 =(int4, int4),
+ FUNCTION 1 hashint4(int4);
diff --git a/src/test/modules/dummy_index/dummy_index.c b/src/test/modules/dummy_index/dummy_index.c
new file mode 100644
index 0000000..a0ab84d
--- /dev/null
+++ b/src/test/modules/dummy_index/dummy_index.c
@@ -0,0 +1,194 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_index.c
+ * Dummy index main file.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/dummy_index/dummy_index.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "catalog/index.h"
+#include "access/reloptions.h"
+#include "utils/guc.h"
+#include "dummy_index.h"
+
+
+PG_FUNCTION_INFO_V1(dihandler);
+PG_MODULE_MAGIC;
+
+void
+_PG_init(void)
+
+{
+ create_reloptions_table();
+ create_reloptions_test_GUC();
+}
+/*
+ * Dummy index handler function: return IndexAmRoutine with access method
+ * parameters and callbacks.
+ */
+Datum
+dihandler(PG_FUNCTION_ARGS)
+{
+ IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
+
+ amroutine->amstrategies = 0;
+ amroutine->amsupport = 1;
+ amroutine->amcanorder = false;
+ amroutine->amcanorderbyop = false;
+ amroutine->amcanbackward = false;
+ amroutine->amcanunique = false;
+ amroutine->amcanmulticol = false;
+ amroutine->amoptionalkey = false;
+ amroutine->amsearcharray = false;
+ amroutine->amsearchnulls = false;
+ amroutine->amstorage = false;
+ amroutine->amclusterable = false;
+ amroutine->ampredlocks = false;
+ amroutine->amcanparallel = false;
+ amroutine->amcaninclude = false;
+ amroutine->amkeytype = InvalidOid;
+
+ amroutine->ambuild = dibuild;
+ amroutine->ambuildempty = dibuildempty;
+ amroutine->aminsert = diinsert;
+ amroutine->ambulkdelete = dibulkdelete;
+ amroutine->amvacuumcleanup = divacuumcleanup;
+ amroutine->amcanreturn = NULL;
+ amroutine->amcostestimate = dicostestimate;
+ amroutine->amoptions = dioptions;
+ amroutine->amproperty = NULL;
+ amroutine->amvalidate = divalidate;
+ amroutine->ambeginscan = dibeginscan;
+ amroutine->amrescan = direscan;
+ amroutine->amgettuple = NULL;
+ amroutine->amgetbitmap = NULL;
+ amroutine->amendscan = diendscan;
+ amroutine->ammarkpos = NULL;
+ amroutine->amrestrpos = NULL;
+ amroutine->amestimateparallelscan = NULL;
+ amroutine->aminitparallelscan = NULL;
+ amroutine->amparallelrescan = NULL;
+
+ PG_RETURN_POINTER(amroutine);
+}
+
+IndexBuildResult *
+dibuild(Relation heap, Relation index, IndexInfo *indexInfo)
+{
+ IndexBuildResult *result;
+
+ result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
+
+ /* let's pretend that no tuples were scanned */
+ result->heap_tuples = 0;
+ /* and no index tuples were created (that is true) */
+ result->index_tuples = 0;
+
+ print_reloptions_test_output(index);
+
+ return result;
+}
+
+void
+dibuildempty(Relation index)
+{
+ /* Let's see what will happen if nothing is done here */
+ /* Add tests for reloptions here */
+}
+
+bool
+diinsert(Relation index, Datum *values, bool *isnull,
+ ItemPointer ht_ctid, Relation heapRel,
+ IndexUniqueCheck checkUnique,
+ IndexInfo *indexInfo)
+{
+ /* This is Dummy Index we do nothing on insert :-) */
+ print_reloptions_test_output(index);
+ return false;
+
+}
+
+IndexBulkDeleteResult *
+dibulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
+ IndexBulkDeleteCallback callback, void *callback_state)
+{
+ /* Do not delete anything; Return NULL as we have nothing to pass to
+ * amvacuumcleanup */
+ return NULL;
+}
+
+IndexBulkDeleteResult *
+divacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
+{
+ /* Index have not been modified, so it is absolutely right to return NULL */
+ return NULL;
+}
+
+void
+dicostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
+ Cost *indexStartupCost, Cost *indexTotalCost,
+ Selectivity *indexSelectivity, double *indexCorrelation,
+ double *indexPages)
+{
+ /* Tell planner to never use this index! */
+ *indexStartupCost = 1.0e10; /* AKA disable_cost */
+ *indexTotalCost = 1.0e10; /* AKA disable_cost */
+
+ /* Do not care about the rest */
+ *indexSelectivity = 1;
+ *indexCorrelation = 0;
+ *indexPages = 1;
+}
+
+bytea *
+dioptions(Datum reloptions, bool validate)
+{
+ relopt_value *options;
+ int numoptions;
+ DummyIndexOptions *rdopts;
+
+ /* Parse the user-given reloptions */
+ options = parseRelOptions(reloptions, validate, di_relopt_kind, &numoptions);
+ rdopts = allocateReloptStruct(sizeof(DummyIndexOptions), options, numoptions);
+ fillRelOptions((void *) rdopts, sizeof(DummyIndexOptions), options, numoptions,
+ validate, di_relopt_tab, lengthof(di_relopt_tab));
+
+ return (bytea *) rdopts;
+}
+
+bool
+divalidate(Oid opclassoid)
+{
+ /* Index does not really work so we are happy with any opclass */
+ return true;
+}
+
+IndexScanDesc
+dibeginscan(Relation r, int nkeys, int norderbys)
+{
+ IndexScanDesc scan;
+
+ /* Let's pretend we are doing something, just in case */
+ scan = RelationGetIndexScan(r, nkeys, norderbys);
+ return scan;
+}
+
+void
+direscan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
+ ScanKey orderbys, int norderbys)
+{
+ /* Just do nothing */
+}
+
+void
+diendscan(IndexScanDesc scan)
+{
+ /* Do nothing */
+}
diff --git a/src/test/modules/dummy_index/dummy_index.control b/src/test/modules/dummy_index/dummy_index.control
new file mode 100644
index 0000000..9eac994
--- /dev/null
+++ b/src/test/modules/dummy_index/dummy_index.control
@@ -0,0 +1,5 @@
+# dummy_index extension
+comment = 'dummmy_index access method that does nothin and is used for test purposes'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_index'
+relocatable = true
diff --git a/src/test/modules/dummy_index/dummy_index.h b/src/test/modules/dummy_index/dummy_index.h
new file mode 100644
index 0000000..ddab4e3
--- /dev/null
+++ b/src/test/modules/dummy_index/dummy_index.h
@@ -0,0 +1,66 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_index.h
+ * Header for dummy_index index.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/dummy_index/dummy_index.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _DUMMY_INDEX_H_
+#define _DUMMY_INDEX_H_
+
+#include "nodes/relation.h"
+
+/* Dummy index options */
+typedef struct DummyIndexOptions
+{
+ int32 vl_len_; /* varlena header (do not touch directly!) */
+ int int_option; /* for reloptions test purposes */
+ double real_option; /* for reloptions test purposes */
+ bool bool_option; /* for reloptions test purposes */
+ int string_option_offset; /* for reloptions test purposes */
+ int string_option2_offset; /* for reloptions test purposes */
+} DummyIndexOptions;
+
+extern void _PG_init(void);
+
+/* index access method interface functions */
+extern bool divalidate(Oid opclassoid);
+extern bool diinsert(Relation index, Datum *values, bool *isnull,
+ ItemPointer ht_ctid, Relation heapRel,
+ IndexUniqueCheck checkUnique,
+ struct IndexInfo *indexInfo);
+extern IndexScanDesc dibeginscan(Relation r, int nkeys, int norderbys);
+extern void direscan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
+ ScanKey orderbys, int norderbys);
+extern void diendscan(IndexScanDesc scan);
+extern IndexBuildResult *dibuild(Relation heap, Relation index,
+ struct IndexInfo *indexInfo);
+extern void dibuildempty(Relation index);
+extern IndexBulkDeleteResult *dibulkdelete(IndexVacuumInfo *info,
+ IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback,
+ void *callback_state);
+extern IndexBulkDeleteResult *divacuumcleanup(IndexVacuumInfo *info,
+ IndexBulkDeleteResult *stats);
+extern bytea *dioptions(Datum reloptions, bool validate);
+extern void dicostestimate(PlannerInfo *root, IndexPath *path,
+ double loop_count, Cost *indexStartupCost,
+ Cost *indexTotalCost, Selectivity *indexSelectivity,
+ double *indexCorrelation, double *indexPages);
+
+/* direoptions.c */
+/* Functions and variables needed for reloptions tests*/
+
+extern relopt_parse_elt di_relopt_tab[5];
+extern relopt_kind di_relopt_kind;
+
+extern void create_reloptions_table(void);
+extern void create_reloptions_test_GUC(void);
+extern void print_reloptions_test_output(Relation index);
+extern void validate_string_option(const char *value);
+
+#endif
diff --git a/src/test/modules/dummy_index/expected/reloptions.out b/src/test/modules/dummy_index/expected/reloptions.out
new file mode 100644
index 0000000..33bccba
--- /dev/null
+++ b/src/test/modules/dummy_index/expected/reloptions.out
@@ -0,0 +1,97 @@
+CREATE EXTENSION dummy_index;
+SET dummy_index.do_test_reloptions to true;
+CREATE TABLE tst (i int4);
+-- Test reloptions behavior when no reloption is set
+CREATE INDEX test_idx ON tst USING dummy_index (i);
+WARNING: No reloptions is set, default values will be chosen in module runtime
+DROP INDEX test_idx;
+-- Test behavior of int option (default and non default values)
+SET dummy_index.do_test_reloption_int to true;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (bool_option = false);
+WARNING: int_option = 10
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (int_option = 5);
+WARNING: int_option = 5
+ALTER INDEX test_idx SET (int_option = 3);
+INSERT INTO tst VALUES(1);
+WARNING: int_option = 3
+ALTER INDEX test_idx SET (bool_option = false);
+ALTER INDEX test_idx RESET (int_option);
+INSERT INTO tst VALUES(1);
+WARNING: int_option = 10
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_int to false;
+-- Test behavior of real option (default and non default values)
+SET dummy_index.do_test_reloption_real to true;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (bool_option = false);
+WARNING: real_option = 3.141500
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (real_option = 5);
+WARNING: real_option = 5.000000
+ALTER INDEX test_idx SET (real_option = 3);
+INSERT INTO tst VALUES(1);
+WARNING: real_option = 3.000000
+ALTER INDEX test_idx SET (bool_option = false);
+ALTER INDEX test_idx RESET (real_option);
+INSERT INTO tst VALUES(1);
+WARNING: real_option = 3.141500
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_real to false;
+-- Test behavior of bool option (default and non default values)
+SET dummy_index.do_test_reloption_bool to true;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (int_option = 5);
+WARNING: bool_option = 1
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (bool_option = false);
+WARNING: bool_option = 0
+ALTER INDEX test_idx SET (bool_option = true);
+INSERT INTO tst VALUES(1);
+WARNING: bool_option = 1
+ALTER INDEX test_idx SET (int_option = 5, bool_option = false);
+ALTER INDEX test_idx RESET (bool_option);
+INSERT INTO tst VALUES(1);
+WARNING: bool_option = 1
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_bool to false;
+-- Test behavior of string option (default and non default values + valudate
+-- function)
+SET dummy_index.do_test_reloption_string to true;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (int_option = 5);
+WARNING: string_option = 'DefaultValue'
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (string_option =
+"Invalid_value");
+WARNING: Validating string option 'Invalid_value'
+ERROR: This seems to be invalid value. Please set valid value
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (string_option =
+"Valid_value");
+WARNING: Validating string option 'Valid_value'
+WARNING: string_option = 'Valid_value'
+ALTER INDEX test_idx SET (string_option = "Valid_value_2", int_option = 5);
+WARNING: Validating string option 'Valid_value_2'
+INSERT INTO tst VALUES(1);
+WARNING: string_option = 'Valid_value_2'
+ALTER INDEX test_idx RESET (string_option);
+INSERT INTO tst VALUES(1);
+WARNING: string_option = 'DefaultValue'
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_string to false;
+-- Test behavior of second string option (there can be issues with second one)
+-- Testing default and non default values + _no_ valudate function)
+SET dummy_index.do_test_reloption_string2 to true;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (string_option =
+"Something");
+WARNING: string_option2 = 'SecondDefaultValue'
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (string_option2 =
+"Some_value");
+WARNING: string_option2 = 'Some_value'
+ALTER INDEX test_idx SET (string_option2 = "Valid_value_2", int_option = 5);
+INSERT INTO tst VALUES(1);
+WARNING: string_option2 = 'Valid_value_2'
+ALTER INDEX test_idx RESET (string_option2);
+INSERT INTO tst VALUES(1);
+WARNING: string_option2 = 'SecondDefaultValue'
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_string2 to false;
+SET dummy_index.do_test_reloptions to false;
diff --git a/src/test/modules/dummy_index/sql/reloptions.sql b/src/test/modules/dummy_index/sql/reloptions.sql
new file mode 100644
index 0000000..3b33ee7
--- /dev/null
+++ b/src/test/modules/dummy_index/sql/reloptions.sql
@@ -0,0 +1,101 @@
+CREATE EXTENSION dummy_index;
+
+SET dummy_index.do_test_reloptions to true;
+
+CREATE TABLE tst (i int4);
+
+-- Test reloptions behavior when no reloption is set
+CREATE INDEX test_idx ON tst USING dummy_index (i);
+DROP INDEX test_idx;
+
+-- Test behavior of int option (default and non default values)
+SET dummy_index.do_test_reloption_int to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (bool_option = false);
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (int_option = 5);
+
+ALTER INDEX test_idx SET (int_option = 3);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx SET (bool_option = false);
+ALTER INDEX test_idx RESET (int_option);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_int to false;
+
+-- Test behavior of real option (default and non default values)
+SET dummy_index.do_test_reloption_real to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (bool_option = false);
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (real_option = 5);
+
+ALTER INDEX test_idx SET (real_option = 3);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx SET (bool_option = false);
+ALTER INDEX test_idx RESET (real_option);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_real to false;
+
+-- Test behavior of bool option (default and non default values)
+SET dummy_index.do_test_reloption_bool to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (int_option = 5);
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (bool_option = false);
+
+ALTER INDEX test_idx SET (bool_option = true);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx SET (int_option = 5, bool_option = false);
+ALTER INDEX test_idx RESET (bool_option);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_bool to false;
+
+-- Test behavior of string option (default and non default values + valudate
+-- function)
+SET dummy_index.do_test_reloption_string to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (int_option = 5);
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (string_option =
+"Invalid_value");
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (string_option =
+"Valid_value");
+
+ALTER INDEX test_idx SET (string_option = "Valid_value_2", int_option = 5);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx RESET (string_option);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_string to false;
+
+-- Test behavior of second string option (there can be issues with second one)
+-- Testing default and non default values + _no_ valudate function)
+SET dummy_index.do_test_reloption_string2 to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (string_option =
+"Something");
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index (i) WITH (string_option2 =
+"Some_value");
+
+ALTER INDEX test_idx SET (string_option2 = "Valid_value_2", int_option = 5);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx RESET (string_option2);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_string2 to false;
+SET dummy_index.do_test_reloptions to false;
On Mon, Mar 18, 2019 at 10:41:13PM +0300, Nikolay Shaplov wrote:
So I created src/test/modules/dummy_index, it does no real indexing, but it
has all types of reloptions that can be set (reloption_int, reloption_real,
reloption_bool, reloption_string and reloption_string2). It also has set of
boolean GUC variables that enables test output concerning certain reloption:
(do_test_reloption_int, do_test_reloption_real, do_test_reloption_bool and
do_test_reloption_string and do_test_reloption_string2) also set
do_test_reloptions to true to get any output at all.
Dummy index will print this output when index is created, and when record is
inserted (this needed to check if your ALTER TABLE did well)
Then you just use normal regression tests: turns on test output, sets some
reloption and check test output, that it properly reaches the access method
internals.
Thanks for doing the effort to split that stuff. This looks like an
interesting base template for anybody willing to look after some
basics with index AMs, like what's done for FDWs with blackhole_fdw.
Perhaps the name should be dummy_am_index or dummy_index_am?
dummy_index does not sound bad either.
While writing this module I kept in mind the idea that this module can be also
used for other am-related tests, so I separated the code into two parts:
dummy_index.c has only code related to implementation of an empty access
method, and all code related to reloptions tests were stored into
direloptions.c. So in future somebody can add di[what_ever_he_wants].c whith
his own tests code, add necessary calls to dummy_index.c, create some GUC
variables, and has his own feature tested.
Here are some comments. I think that this could be simplified
further more.
The README file could have a more consistent format with the rest.
See for example dummy_seclabel/README. You could add a small
example with its usage.
Is there any point in having string_option2? String reloptions are
already tested with string_option. Also => s/Seconf/Second/.
s/valudate/validate/.
+-- Test behavior of second string option (there can be issues with second one)
What are those issues?
+ } else
+ {
Code format does not follow the Postgres guidelines. You could fix
all that with an indent run.
The ranges of the different values are not tested, wouldn't it be
better to test that as well?
The way the test is configured with the strong dependencies between
the reloption types and the GUCs is much bug-prone I think. All of
that is present only to print a couple of WARNING messages with
specific values of values. So, why not removing the GUCs and the
printing logic which shows a subset of values? Please note that these
are visible directly via pg_class.reloptions. So we could shave quite
some code.
Please note that the compilation of the module fails.
nodes/relation.h maybe is access/relation.h? You may want to review
all that.
--
Michael
В письме от вторник, 19 марта 2019 г. 16:09:13 MSK пользователь Michael
Paquier написал:
So I created src/test/modules/dummy_index, it does no real indexing, but
it
has all types of reloptions that can be set (reloption_int,
reloption_real,
reloption_bool, reloption_string and reloption_string2). It also has set
of
boolean GUC variables that enables test output concerning certain
reloption: (do_test_reloption_int, do_test_reloption_real,
do_test_reloption_bool and do_test_reloption_string and
do_test_reloption_string2) also set
do_test_reloptions to true to get any output at all.
Dummy index will print this output when index is created, and when record
is inserted (this needed to check if your ALTER TABLE did well)
Then you just use normal regression tests: turns on test output, sets some
reloption and check test output, that it properly reaches the access
method
internals.Thanks for doing the effort to split that stuff. This looks like an
interesting base template for anybody willing to look after some
basics with index AMs, like what's done for FDWs with blackhole_fdw.
I am not sure it is good template. Most methods are empty, and does not show
any example of how it should work.
If I am to create a template I would try to create index that just do seq scan
of indexed values. It would have all code index must have, but the code of the
index algorithm iteslf would be minimal. But it is another task.
Perhaps the name should be dummy_am_index or dummy_index_am?
dummy_index does not sound bad either.
Actually I do not see any reason to do it, all indexes in postgres are
implemented as access methods, so it sounds as double naming for me. But I
actually do not care about this name, if you think adding _am is better, so I
did it.
But i did not remove .c file names and did not change di- suffix to dia- in the
code. Is it ok for you?
While writing this module I kept in mind the idea that this module can be
also used for other am-related tests, so I separated the code into two
parts: dummy_index.c has only code related to implementation of an empty
access method, and all code related to reloptions tests were stored into
direloptions.c. So in future somebody can add di[what_ever_he_wants].c
whith his own tests code, add necessary calls to dummy_index.c, create
some GUC variables, and has his own feature tested.Here are some comments. I think that this could be simplified
further more.The README file could have a more consistent format with the rest.
See for example dummy_seclabel/README. You could add a small
example with its usage.
Good notion. Fixed it.
Is there any point in having string_option2? String reloptions are
already tested with string_option.
There are two reasons for that:
1. We should test both behavior with validation function, and without one. For
this we need two options, because we can change this in runtime
2. The implementation of string functions is a bit tricky. It allocates some
more memory after the Option structure, and string values are placed there. It
works well with one string option, but I was not sure that is works properly
for two of them. I can imagine a bug that will show itself only with a second
option. So we anyway should test two.
Also => s/Seconf/Second/.
s/valudate/validate/.
Thanks. I tried my best with aspell, but still missed something.
+-- Test behavior of second string option (there can be issues with second
one) What are those issues?
This issues are listed in README. And also I've written them above. To prevent
confusion I've removed this issue notion. :-) One who want to know more, can
read README file ;-)
+ } else + { Code format does not follow the Postgres guidelines. You could fix all that with an indent run.
Oups, it's my favorite codestyle, I fall back to it when does not pay
attention. I've reindented the code, a good idea. Should come to it myself....
The ranges of the different values are not tested, wouldn't it be
better to test that as well?
My idea was to test only things that can't be tested in regression tests.
Ranges are tested in regression tests ( I also wrote that tests) and it is
better to leave it there.
But the question is good, I would mention it in README file, to make it
clear....
The way the test is configured with the strong dependencies between
the reloption types and the GUCs is much bug-prone I think. All of
that is present only to print a couple of WARNING messages with
specific values of values. So, why not removing the GUCs and the
printing logic which shows a subset of values?
I am afraid that we will get a mess that will work well, but it would be
difficult for a human to find any logic in the output. And sooner or later we
will need it, when something will go wrong and somebody will try to find out
why.
So it is better to test one option at a time, and that's why mute test output
for other options.
Please note that these
are visible directly via pg_class.reloptions. So we could shave quite
some code.
Values from pg_class are well tested in regression test. My point here is to
check that they reach index internal as expected. And there is a long way
between pg_class.reloptions and index internals.
Please note that the compilation of the module fails.
nodes/relation.h maybe is access/relation.h? You may want to review
all that.
Hm... I do not quite understand how it get there and why in worked for me
before. But I changed it to nodes/pathnodes.h It was here because it is needed
for PlannerInfo symbol.
PS. Sorry for long delays. I do not always have much time to do postgres...
Attachments:
dummy_index_v2.difftext/x-patch; charset=UTF-8; name=dummy_index_v2.diffDownload
diff --git a/.gitignore b/.gitignore
index 794e35b..37331c2 100644
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index dfd0956..742078e 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -7,6 +7,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = \
brin \
commit_ts \
+ dummy_index_am \
dummy_seclabel \
snapshot_too_old \
test_bloomfilter \
diff --git a/src/test/modules/dummy_index_am/.gitignore b/src/test/modules/dummy_index_am/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/dummy_index_am/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/dummy_index_am/Makefile b/src/test/modules/dummy_index_am/Makefile
new file mode 100644
index 0000000..695941b
--- /dev/null
+++ b/src/test/modules/dummy_index_am/Makefile
@@ -0,0 +1,21 @@
+# contrib/bloom/Makefile
+
+MODULE_big = dummy_index_am
+OBJS = dummy_index.o direloptions.o $(WIN32RES)
+
+EXTENSION = dummy_index_am
+DATA = dummy_index_am--1.0.sql
+PGFILEDESC = "dummy index access method - needed for all kinds of tests"
+
+REGRESS = reloptions
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_index_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_index_am/README b/src/test/modules/dummy_index_am/README
new file mode 100644
index 0000000..61f80d3
--- /dev/null
+++ b/src/test/modules/dummy_index_am/README
@@ -0,0 +1,45 @@
+Dummy index AM
+==============
+
+Dummy index, an index module for testing am-related internal calls that can't be
+properly tested inside production indexes, but better to be tested inside actual
+access method module.
+
+Guidelines:
+1. Keep this index as simple as as possible. It should not do any real indexing
+(unless you need it for your tests), if you need it make it as simple as
+possible;
+2. Keep all code related to feature you test inside id[name_of_a_feature].c
+file. dummy_index.c file should contain code that is needed for everyone, and
+make calls to feature-related code;
+3. Code related to your feature tests should be enabled and disabled by GUC
+variables. Create as many boolean GUC variables as you need and set them on so
+your test will know when it is time to do test output. Thus output from
+different features tests are not interfered;
+4. Add section to this README file that describes the general idea of what your
+tests do.
+
+Reloptions tests
+================
+
+Some relotions testing are done in regression tests. There we can check if new
+option value properly reaches pg_class, minimum/maximum checks, etc. But we
+can't check if option value successfully reached index internal code when we are
+dealing with production indexes. But we can do it here in Dummy index.
+Here we check that values of reloptions properly reaches am-internals, and that
+all reloption-related code works as expected. Several GUC variables are defined
+to control test's output. do_test_reloptions turns on and off any
+reloptions-related output. You will also get some test output in the case when
+no relation options is set at all. do_test_reloption_int,
+do_test_reloption_real, do_test_reloption_bool and do_test_reloption_string
+allows to enable test output for int, real, boolean and string options.
+do_test_reloption_string2 enables test output for another string option. We need
+second string option, because a) string reloption implementation is a bit
+tricky, and it is better to check that second value works as well; b) better to
+check both cases: string option with validation function and without one, so we
+need two string options.
+
+
+Authors
+=======
+Nikolay Shaplov <dhyan@nataraj.su> (Dummy index core, reloptions tests)
diff --git a/src/test/modules/dummy_index_am/direloptions.c b/src/test/modules/dummy_index_am/direloptions.c
new file mode 100644
index 0000000..3c7cca5
--- /dev/null
+++ b/src/test/modules/dummy_index_am/direloptions.c
@@ -0,0 +1,219 @@
+/*-------------------------------------------------------------------------
+ *
+ * direloptions.c
+ * Functions and variables needed for reloptions tests.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/dummy_index_am/direloptions.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "utils/rel.h"
+#include "access/reloptions.h"
+#include "utils/guc.h"
+
+#include "dummy_index.h"
+
+
+/* parse table for fillRelOptions */
+relopt_parse_elt di_relopt_tab[5];
+
+
+/* Kind of relation options for dummy index */
+relopt_kind di_relopt_kind;
+
+/* GUC variables */
+static bool do_test_reloptions = false;
+static bool do_test_reloption_int = false;
+static bool do_test_reloption_real = false;
+static bool do_test_reloption_bool = false;
+static bool do_test_reloption_string = false;
+static bool do_test_reloption_string2 = false;
+
+/*
+ * This function creates reloptions that would be used in reloptions tests
+ */
+void
+create_reloptions_table()
+{
+ di_relopt_kind = add_reloption_kind();
+
+ add_int_reloption(di_relopt_kind, "int_option",
+ "Int option used for relotions test purposes",
+ 10, -10, 100);
+ di_relopt_tab[0].optname = "int_option";
+ di_relopt_tab[0].opttype = RELOPT_TYPE_INT;
+ di_relopt_tab[0].offset = offsetof(DummyIndexOptions, int_option);
+
+ add_real_reloption(di_relopt_kind, "real_option",
+ "Real option used for relotions test purposes",
+ 3.1415, -10, 100);
+ di_relopt_tab[1].optname = "real_option";
+ di_relopt_tab[1].opttype = RELOPT_TYPE_REAL;
+ di_relopt_tab[1].offset = offsetof(DummyIndexOptions, real_option);
+
+
+ add_bool_reloption(di_relopt_kind, "bool_option",
+ "Boolean option used for relotions test purposes",
+ true);
+ di_relopt_tab[2].optname = "bool_option";
+ di_relopt_tab[2].opttype = RELOPT_TYPE_BOOL;
+ di_relopt_tab[2].offset = offsetof(DummyIndexOptions, bool_option);
+
+ add_string_reloption(di_relopt_kind, "string_option",
+ "String option used for relotions test purposes",
+ "DefaultValue", &validate_string_option);
+ di_relopt_tab[3].optname = "string_option";
+ di_relopt_tab[3].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[3].offset = offsetof(DummyIndexOptions, string_option_offset);
+
+ add_string_reloption(di_relopt_kind, "string_option2",
+ "Second string option used for relotions test purposes",
+ "SecondDefaultValue", &validate_string_option);
+ di_relopt_tab[4].optname = "string_option2";
+ di_relopt_tab[4].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[4].offset = offsetof(DummyIndexOptions, string_option2_offset);
+
+}
+
+/*
+ * This function creates GUC variables that allows to turn on and off test
+ * output for different relopntions
+ */
+void
+create_reloptions_test_GUC()
+{
+ DefineCustomBoolVariable("dummy_index.do_test_reloptions",
+ "Set to true if you are going to test reloptions.",
+ NULL,
+ &do_test_reloptions,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_int",
+ "Set to true if you are going to test int reloption.",
+ NULL,
+ &do_test_reloption_int,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_real",
+ "Set to true if you are going to test real reloption.",
+ NULL,
+ &do_test_reloption_real,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_bool",
+ "Set to true if you are going to test boolean reloption.",
+ NULL,
+ &do_test_reloption_bool,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_string",
+ "Set to true if you are going to test string reloption.",
+ NULL,
+ &do_test_reloption_string,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+ DefineCustomBoolVariable("dummy_index.do_test_reloption_string2",
+ "Set to true if you are going to test second string reloption.",
+ NULL,
+ &do_test_reloption_string2,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+}
+
+/*
+ * Function prints output for reloptions tests if GUC variables switches are
+ * properly set on.
+ */
+void
+print_reloptions_test_output(Relation index)
+{
+ if (do_test_reloptions)
+ {
+ if (!index->rd_options)
+ {
+ elog(WARNING, "No reloptions is set, default values will be chosen in module runtime");
+ }
+ else
+ {
+ DummyIndexOptions *opt;
+
+ opt = (DummyIndexOptions *) index->rd_options;
+ if (do_test_reloption_int)
+ {
+ elog(WARNING, "int_option = %d", opt->int_option);
+ }
+ if (do_test_reloption_real)
+ {
+ elog(WARNING, "real_option = %f", opt->real_option);
+ }
+ if (do_test_reloption_bool)
+ {
+ elog(WARNING, "bool_option = %d", opt->bool_option);
+ }
+ if (do_test_reloption_string)
+ {
+ char *str;
+
+ str = (char *) opt + opt->string_option_offset;
+ elog(WARNING, "string_option = '%s'", str);
+ }
+ if (do_test_reloption_string2)
+ {
+ char *str;
+
+ str = (char *) opt + opt->string_option2_offset;
+ elog(WARNING, "string_option2 = '%s'", str);
+ }
+ }
+ }
+}
+
+/*
+ * Validation function for string_option reloption
+ */
+void
+validate_string_option(const char *value)
+{
+ if (do_test_reloptions && do_test_reloption_string)
+ {
+ if (value)
+ elog(WARNING, "Validating string option '%s'", value);
+ else
+ elog(WARNING, "Validating string option with NULL value");
+ }
+ if (!value || *value == 'I')
+ elog(ERROR, "This seems to be invalid value. Please set valid value");
+}
diff --git a/src/test/modules/dummy_index_am/dummy_index.c b/src/test/modules/dummy_index_am/dummy_index.c
new file mode 100644
index 0000000..bd4c665
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index.c
@@ -0,0 +1,197 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_index.c
+ * Dummy index AM main file.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/dummy_index_am/dummy_index.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "catalog/index.h"
+#include "access/reloptions.h"
+#include "utils/guc.h"
+#include "dummy_index.h"
+
+
+PG_FUNCTION_INFO_V1(dihandler);
+PG_MODULE_MAGIC;
+
+void
+_PG_init(void)
+
+{
+ create_reloptions_table();
+ create_reloptions_test_GUC();
+}
+
+/*
+ * Dummy index handler function: return IndexAmRoutine with access method
+ * parameters and callbacks.
+ */
+Datum
+dihandler(PG_FUNCTION_ARGS)
+{
+ IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
+
+ amroutine->amstrategies = 0;
+ amroutine->amsupport = 1;
+ amroutine->amcanorder = false;
+ amroutine->amcanorderbyop = false;
+ amroutine->amcanbackward = false;
+ amroutine->amcanunique = false;
+ amroutine->amcanmulticol = false;
+ amroutine->amoptionalkey = false;
+ amroutine->amsearcharray = false;
+ amroutine->amsearchnulls = false;
+ amroutine->amstorage = false;
+ amroutine->amclusterable = false;
+ amroutine->ampredlocks = false;
+ amroutine->amcanparallel = false;
+ amroutine->amcaninclude = false;
+ amroutine->amkeytype = InvalidOid;
+
+ amroutine->ambuild = dibuild;
+ amroutine->ambuildempty = dibuildempty;
+ amroutine->aminsert = diinsert;
+ amroutine->ambulkdelete = dibulkdelete;
+ amroutine->amvacuumcleanup = divacuumcleanup;
+ amroutine->amcanreturn = NULL;
+ amroutine->amcostestimate = dicostestimate;
+ amroutine->amoptions = dioptions;
+ amroutine->amproperty = NULL;
+ amroutine->amvalidate = divalidate;
+ amroutine->ambeginscan = dibeginscan;
+ amroutine->amrescan = direscan;
+ amroutine->amgettuple = NULL;
+ amroutine->amgetbitmap = NULL;
+ amroutine->amendscan = diendscan;
+ amroutine->ammarkpos = NULL;
+ amroutine->amrestrpos = NULL;
+ amroutine->amestimateparallelscan = NULL;
+ amroutine->aminitparallelscan = NULL;
+ amroutine->amparallelrescan = NULL;
+
+ PG_RETURN_POINTER(amroutine);
+}
+
+IndexBuildResult *
+dibuild(Relation heap, Relation index, IndexInfo *indexInfo)
+{
+ IndexBuildResult *result;
+
+ result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
+
+ /* let's pretend that no tuples were scanned */
+ result->heap_tuples = 0;
+ /* and no index tuples were created (that is true) */
+ result->index_tuples = 0;
+
+ print_reloptions_test_output(index);
+
+ return result;
+}
+
+void
+dibuildempty(Relation index)
+{
+ /* Let's see what will happen if nothing is done here */
+ /* Add tests for reloptions here */
+}
+
+bool
+diinsert(Relation index, Datum *values, bool *isnull,
+ ItemPointer ht_ctid, Relation heapRel,
+ IndexUniqueCheck checkUnique,
+ IndexInfo *indexInfo)
+{
+ /* This is Dummy Index we do nothing on insert :-) */
+ print_reloptions_test_output(index);
+ return false;
+
+}
+
+IndexBulkDeleteResult *
+dibulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
+ IndexBulkDeleteCallback callback, void *callback_state)
+{
+ /*
+ * Do not delete anything; Return NULL as we have nothing to pass to
+ * amvacuumcleanup
+ */
+ return NULL;
+}
+
+IndexBulkDeleteResult *
+divacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
+{
+ /* Index have not been modified, so it is absolutely right to return NULL */
+ return NULL;
+}
+
+void
+dicostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
+ Cost *indexStartupCost, Cost *indexTotalCost,
+ Selectivity *indexSelectivity, double *indexCorrelation,
+ double *indexPages)
+{
+ /* Tell planner to never use this index! */
+ *indexStartupCost = 1.0e10; /* AKA disable_cost */
+ *indexTotalCost = 1.0e10; /* AKA disable_cost */
+
+ /* Do not care about the rest */
+ *indexSelectivity = 1;
+ *indexCorrelation = 0;
+ *indexPages = 1;
+}
+
+bytea *
+dioptions(Datum reloptions, bool validate)
+{
+ relopt_value *options;
+ int numoptions;
+ DummyIndexOptions *rdopts;
+
+ /* Parse the user-given reloptions */
+ options = parseRelOptions(reloptions, validate, di_relopt_kind, &numoptions);
+ rdopts = allocateReloptStruct(sizeof(DummyIndexOptions), options, numoptions);
+ fillRelOptions((void *) rdopts, sizeof(DummyIndexOptions), options, numoptions,
+ validate, di_relopt_tab, lengthof(di_relopt_tab));
+
+ return (bytea *) rdopts;
+}
+
+bool
+divalidate(Oid opclassoid)
+{
+ /* Index does not really work so we are happy with any opclass */
+ return true;
+}
+
+IndexScanDesc
+dibeginscan(Relation r, int nkeys, int norderbys)
+{
+ IndexScanDesc scan;
+
+ /* Let's pretend we are doing something, just in case */
+ scan = RelationGetIndexScan(r, nkeys, norderbys);
+ return scan;
+}
+
+void
+direscan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
+ ScanKey orderbys, int norderbys)
+{
+ /* Just do nothing */
+}
+
+void
+diendscan(IndexScanDesc scan)
+{
+ /* Do nothing */
+}
diff --git a/src/test/modules/dummy_index_am/dummy_index.h b/src/test/modules/dummy_index_am/dummy_index.h
new file mode 100644
index 0000000..fa6e405
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index.h
@@ -0,0 +1,66 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_index.h
+ * Header for dummy_index_am index.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/dummy_index_am/dummy_index.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _DUMMY_INDEX_H_
+#define _DUMMY_INDEX_H_
+
+#include "nodes/pathnodes.h"
+
+/* Dummy index options */
+typedef struct DummyIndexOptions
+{
+ int32 vl_len_; /* varlena header (do not touch directly!) */
+ int int_option; /* for reloptions test purposes */
+ double real_option; /* for reloptions test purposes */
+ bool bool_option; /* for reloptions test purposes */
+ int string_option_offset; /* for reloptions test purposes */
+ int string_option2_offset; /* for reloptions test purposes */
+} DummyIndexOptions;
+
+extern void _PG_init(void);
+
+/* index access method interface functions */
+extern bool divalidate(Oid opclassoid);
+extern bool diinsert(Relation index, Datum *values, bool *isnull,
+ ItemPointer ht_ctid, Relation heapRel,
+ IndexUniqueCheck checkUnique,
+ struct IndexInfo *indexInfo);
+extern IndexScanDesc dibeginscan(Relation r, int nkeys, int norderbys);
+extern void direscan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
+ ScanKey orderbys, int norderbys);
+extern void diendscan(IndexScanDesc scan);
+extern IndexBuildResult *dibuild(Relation heap, Relation index,
+ struct IndexInfo *indexInfo);
+extern void dibuildempty(Relation index);
+extern IndexBulkDeleteResult *dibulkdelete(IndexVacuumInfo *info,
+ IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback,
+ void *callback_state);
+extern IndexBulkDeleteResult *divacuumcleanup(IndexVacuumInfo *info,
+ IndexBulkDeleteResult *stats);
+extern bytea *dioptions(Datum reloptions, bool validate);
+extern void dicostestimate(PlannerInfo *root, IndexPath *path,
+ double loop_count, Cost *indexStartupCost,
+ Cost *indexTotalCost, Selectivity *indexSelectivity,
+ double *indexCorrelation, double *indexPages);
+
+/* direoptions.c */
+/* Functions and variables needed for reloptions tests*/
+
+extern relopt_parse_elt di_relopt_tab[5];
+extern relopt_kind di_relopt_kind;
+
+extern void create_reloptions_table(void);
+extern void create_reloptions_test_GUC(void);
+extern void print_reloptions_test_output(Relation index);
+extern void validate_string_option(const char *value);
+
+#endif
diff --git a/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql b/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql
new file mode 100644
index 0000000..8049c43
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql
@@ -0,0 +1,20 @@
+/* src/test/modules/dummy_index/dummy_index_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_index_am" to load this file. \quit
+
+CREATE FUNCTION dihandler(internal)
+RETURNS index_am_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+-- Access method
+CREATE ACCESS METHOD dummy_index_am TYPE INDEX HANDLER dihandler;
+COMMENT ON ACCESS METHOD dummy_index_am IS 'dummy index am is access method for test purposes';
+
+-- Opclasses
+
+CREATE OPERATOR CLASS int4_ops
+DEFAULT FOR TYPE int4 USING dummy_index_am AS
+ OPERATOR 1 =(int4, int4),
+ FUNCTION 1 hashint4(int4);
diff --git a/src/test/modules/dummy_index_am/dummy_index_am.control b/src/test/modules/dummy_index_am/dummy_index_am.control
new file mode 100644
index 0000000..93fb57b
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am.control
@@ -0,0 +1,5 @@
+# dummy_index extension
+comment = 'dummmy_index_am access method that does nothin and is used for test purposes'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_index_am'
+relocatable = true
diff --git a/src/test/modules/dummy_index_am/expected/reloptions.out b/src/test/modules/dummy_index_am/expected/reloptions.out
new file mode 100644
index 0000000..f80b2fc
--- /dev/null
+++ b/src/test/modules/dummy_index_am/expected/reloptions.out
@@ -0,0 +1,97 @@
+CREATE EXTENSION dummy_index_am;
+SET dummy_index.do_test_reloptions to true;
+CREATE TABLE tst (i int4);
+-- Test reloptions behavior when no reloption is set
+CREATE INDEX test_idx ON tst USING dummy_index_am (i);
+WARNING: No reloptions is set, default values will be chosen in module runtime
+DROP INDEX test_idx;
+-- Test behavior of int option (default and non default values)
+SET dummy_index.do_test_reloption_int to true;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (bool_option = false);
+WARNING: int_option = 10
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (int_option = 5);
+WARNING: int_option = 5
+ALTER INDEX test_idx SET (int_option = 3);
+INSERT INTO tst VALUES(1);
+WARNING: int_option = 3
+ALTER INDEX test_idx SET (bool_option = false);
+ALTER INDEX test_idx RESET (int_option);
+INSERT INTO tst VALUES(1);
+WARNING: int_option = 10
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_int to false;
+-- Test behavior of real option (default and non default values)
+SET dummy_index.do_test_reloption_real to true;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (bool_option = false);
+WARNING: real_option = 3.141500
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (real_option = 5);
+WARNING: real_option = 5.000000
+ALTER INDEX test_idx SET (real_option = 3);
+INSERT INTO tst VALUES(1);
+WARNING: real_option = 3.000000
+ALTER INDEX test_idx SET (bool_option = false);
+ALTER INDEX test_idx RESET (real_option);
+INSERT INTO tst VALUES(1);
+WARNING: real_option = 3.141500
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_real to false;
+-- Test behavior of bool option (default and non default values)
+SET dummy_index.do_test_reloption_bool to true;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (int_option = 5);
+WARNING: bool_option = 1
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (bool_option = false);
+WARNING: bool_option = 0
+ALTER INDEX test_idx SET (bool_option = true);
+INSERT INTO tst VALUES(1);
+WARNING: bool_option = 1
+ALTER INDEX test_idx SET (int_option = 5, bool_option = false);
+ALTER INDEX test_idx RESET (bool_option);
+INSERT INTO tst VALUES(1);
+WARNING: bool_option = 1
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_bool to false;
+-- Test behavior of string option (default and non default values + validate
+-- function)
+SET dummy_index.do_test_reloption_string to true;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (int_option = 5);
+WARNING: string_option = 'DefaultValue'
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (string_option =
+"Invalid_value");
+WARNING: Validating string option 'Invalid_value'
+ERROR: This seems to be invalid value. Please set valid value
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (string_option =
+"Valid_value");
+WARNING: Validating string option 'Valid_value'
+WARNING: string_option = 'Valid_value'
+ALTER INDEX test_idx SET (string_option = "Valid_value_2", int_option = 5);
+WARNING: Validating string option 'Valid_value_2'
+INSERT INTO tst VALUES(1);
+WARNING: string_option = 'Valid_value_2'
+ALTER INDEX test_idx RESET (string_option);
+INSERT INTO tst VALUES(1);
+WARNING: string_option = 'DefaultValue'
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_string to false;
+-- Test behavior of second string option
+-- Testing default and non default values + _no_ validate function)
+SET dummy_index.do_test_reloption_string2 to true;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (string_option =
+"Something");
+WARNING: string_option2 = 'SecondDefaultValue'
+DROP INDEX test_idx;
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (string_option2 =
+"Some_value");
+WARNING: string_option2 = 'Some_value'
+ALTER INDEX test_idx SET (string_option2 = "Valid_value_2", int_option = 5);
+INSERT INTO tst VALUES(1);
+WARNING: string_option2 = 'Valid_value_2'
+ALTER INDEX test_idx RESET (string_option2);
+INSERT INTO tst VALUES(1);
+WARNING: string_option2 = 'SecondDefaultValue'
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_string2 to false;
+SET dummy_index.do_test_reloptions to false;
diff --git a/src/test/modules/dummy_index_am/sql/reloptions.sql b/src/test/modules/dummy_index_am/sql/reloptions.sql
new file mode 100644
index 0000000..804a358
--- /dev/null
+++ b/src/test/modules/dummy_index_am/sql/reloptions.sql
@@ -0,0 +1,101 @@
+CREATE EXTENSION dummy_index_am;
+
+SET dummy_index.do_test_reloptions to true;
+
+CREATE TABLE tst (i int4);
+
+-- Test reloptions behavior when no reloption is set
+CREATE INDEX test_idx ON tst USING dummy_index_am (i);
+DROP INDEX test_idx;
+
+-- Test behavior of int option (default and non default values)
+SET dummy_index.do_test_reloption_int to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (bool_option = false);
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (int_option = 5);
+
+ALTER INDEX test_idx SET (int_option = 3);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx SET (bool_option = false);
+ALTER INDEX test_idx RESET (int_option);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_int to false;
+
+-- Test behavior of real option (default and non default values)
+SET dummy_index.do_test_reloption_real to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (bool_option = false);
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (real_option = 5);
+
+ALTER INDEX test_idx SET (real_option = 3);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx SET (bool_option = false);
+ALTER INDEX test_idx RESET (real_option);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_real to false;
+
+-- Test behavior of bool option (default and non default values)
+SET dummy_index.do_test_reloption_bool to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (int_option = 5);
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (bool_option = false);
+
+ALTER INDEX test_idx SET (bool_option = true);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx SET (int_option = 5, bool_option = false);
+ALTER INDEX test_idx RESET (bool_option);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_bool to false;
+
+-- Test behavior of string option (default and non default values + validate
+-- function)
+SET dummy_index.do_test_reloption_string to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (int_option = 5);
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (string_option =
+"Invalid_value");
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (string_option =
+"Valid_value");
+
+ALTER INDEX test_idx SET (string_option = "Valid_value_2", int_option = 5);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx RESET (string_option);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_string to false;
+
+-- Test behavior of second string option
+-- Testing default and non default values + _no_ validate function)
+SET dummy_index.do_test_reloption_string2 to true;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (string_option =
+"Something");
+DROP INDEX test_idx;
+
+CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (string_option2 =
+"Some_value");
+
+ALTER INDEX test_idx SET (string_option2 = "Valid_value_2", int_option = 5);
+INSERT INTO tst VALUES(1);
+ALTER INDEX test_idx RESET (string_option2);
+INSERT INTO tst VALUES(1);
+
+DROP INDEX test_idx;
+SET dummy_index.do_test_reloption_string2 to false;
+SET dummy_index.do_test_reloptions to false;
Hi Nikolay,
On 3 Apr 2019, at 20:54, Nikolay Shaplov <dhyan@nataraj.su> wrote:
В письме от вторник, 19 марта 2019 г. 16:09:13 MSK пользователь Michael
Paquier написал:Thanks for doing the effort to split that stuff. This looks like an
interesting base template for anybody willing to look after some
basics with index AMs, like what's done for FDWs with blackhole_fdw.I am not sure it is good template. Most methods are empty, and does not show
any example of how it should work.
I think it would probably not be a good template — not for a a solid start point.
There is value in having something that has all the relevant method signatures, just to save someone the bother of crawling docs, or scraping other contrib/ examples for copy/paste snippets. But I think it should really be a different thing. It would be a distraction to litter such a template with custom reloptions clutter.
I guess that assumes it is possible to create a realistic AM without configurable options. I’m guessing it should be. But perhaps such situations are rarer than I imagine…?
Better than an empty template, though, would be a concrete, but minimal, implementation of an INDEX/AM. I find it difficult to see how you get something clear and concise, while trying to simultaneously serve both INDEX/AM template and reloptions testing needs.
Please note that these
are visible directly via pg_class.reloptions. So we could shave quite
some code.Values from pg_class are well tested in regression test. My point here is to
check that they reach index internal as expected. And there is a long way
between pg_class.reloptions and index internals.
I had the same thought. But on quick inspection — and perhaps I have missed something — I don’t see that /custom/ reloptions are really tested at all by the regression tests.
So I do think verifying an extension’s custom reloptions exposure would be valuable.
I guess you might argue that it’s the regression test suite that should properly test that exposure mechanism. I kind of agree. :-) But I think that argument falls for similar reasons you cite for your initiative — i.e., it’s basically pretty hard to set up the situation where any kind of custom reloption would ever be reported.
Hope that is useful feedback.
denty.
On Thu, Jun 27, 2019 at 10:17 AM Dent John <denty@qqdd.eu> wrote:
On 3 Apr 2019, at 20:54, Nikolay Shaplov <dhyan@nataraj.su> wrote:
В письме от вторник, 19 марта 2019 г. 16:09:13 MSK пользователь Michael
Paquier написал:Thanks for doing the effort to split that stuff. This looks like an
interesting base template for anybody willing to look after some
basics with index AMs, like what's done for FDWs with blackhole_fdw.I am not sure it is good template. Most methods are empty, and does not show
any example of how it should work.[review]
Hi Nikolay,
While moving this to the September CF, I noticed this failure:
test reloptions ... FAILED 32 ms
--- /home/travis/build/postgresql-cfbot/postgresql/src/test/modules/dummy_index_am/expected/reloptions.out
2019-08-01 08:06:16.580197980 +0000
+++ /home/travis/build/postgresql-cfbot/postgresql/src/test/modules/dummy_index_am/results/reloptions.out
2019-08-01 08:11:57.817493999 +0000
@@ -13,12 +13,14 @@
CREATE INDEX test_idx ON tst USING dummy_index_am (i) WITH (int_option = 5);
WARNING: int_option = 5
ALTER INDEX test_idx SET (int_option = 3);
+ERROR: unrecognized lock mode: 2139062143
INSERT INTO tst VALUES(1);
-WARNING: int_option = 3
+WARNING: int_option = 5
ALTER INDEX test_idx SET (bool_option = false);
ALTER INDEX test_idx RESET (int_option);
+ERROR: unrecognized lock mode: 2139062143
INSERT INTO tst VALUES(1);
-WARNING: int_option = 10
+WARNING: int_option = 5
DROP INDEX test_idx;
SET dummy_index.do_test_reloption_int to false;
-- Test behavior of real option (default and non default values)
@@ -48,9 +50,10 @@
INSERT INTO tst VALUES(1);
WARNING: bool_option = 1
ALTER INDEX test_idx SET (int_option = 5, bool_option = false);
+ERROR: unrecognized lock mode: 2139062143
ALTER INDEX test_idx RESET (bool_option);
INSERT INTO tst VALUES(1);
-WARNING: bool_option = 1
+WARNING: No reloptions is set, default values will be chosen in module runtime
DROP INDEX test_idx;
SET dummy_index.do_test_reloption_bool to false;
-- Test behavior of string option (default and non default values + validate
@@ -68,12 +71,12 @@
WARNING: Validating string option 'Valid_value'
WARNING: string_option = 'Valid_value'
ALTER INDEX test_idx SET (string_option = "Valid_value_2", int_option = 5);
-WARNING: Validating string option 'Valid_value_2'
+ERROR: unrecognized lock mode: 2139062143
INSERT INTO tst VALUES(1);
-WARNING: string_option = 'Valid_value_2'
+WARNING: string_option = 'Valid_value'
ALTER INDEX test_idx RESET (string_option);
INSERT INTO tst VALUES(1);
-WARNING: string_option = 'DefaultValue'
+WARNING: No reloptions is set, default values will be chosen in module runtime
DROP INDEX test_idx;
SET dummy_index.do_test_reloption_string to false;
-- Test behavior of second string option
@@ -87,11 +90,12 @@
"Some_value");
WARNING: string_option2 = 'Some_value'
ALTER INDEX test_idx SET (string_option2 = "Valid_value_2", int_option = 5);
+ERROR: unrecognized lock mode: 2139062143
INSERT INTO tst VALUES(1);
-WARNING: string_option2 = 'Valid_value_2'
+WARNING: string_option2 = 'Some_value'
ALTER INDEX test_idx RESET (string_option2);
INSERT INTO tst VALUES(1);
-WARNING: string_option2 = 'SecondDefaultValue'
+WARNING: No reloptions is set, default values will be chosen in module runtime
DROP INDEX test_idx;
SET dummy_index.do_test_reloption_string2 to false;
SET dummy_index.do_test_reloptions to false;
--
Thomas Munro
https://enterprisedb.com
В Fri, 2 Aug 2019 11:12:35 +1200
Thomas Munro <thomas.munro@gmail.com> пишет:
While moving this to the September CF, I noticed this failure:
test reloptions ... FAILED 32 ms
Do you have any idea, how to reproduce this? I tried this patch on
current master, and did not get result you are talking about.
Is it still there for you BTW?
On Thu, Sep 19, 2019 at 7:58 AM Nikolay Shaplov <dhyan@nataraj.su> wrote:
В Fri, 2 Aug 2019 11:12:35 +1200
Thomas Munro <thomas.munro@gmail.com> пишет:While moving this to the September CF, I noticed this failure:
test reloptions ... FAILED 32 ms
Do you have any idea, how to reproduce this? I tried this patch on
current master, and did not get result you are talking about.
Is it still there for you BTW?
Hi Nikolay,
Yeah, it's still happening on Travis:
https://travis-ci.org/postgresql-cfbot/postgresql/builds/586714100
Although the "reloptions" tests passes when it's run as part of the
regular test schedule (ie "make check"), the patch also runs it from
src/test/modules/dummy_index_am/Makefile ("REGRESS = reloptions"), and
when it runs in that context it fails. Cfbot is simply running "make
check-world".
Let's see if I can see this on my Mac... yep, with "make -C
src/test/modules/dummy_index_am directory check" I see a similar
failure with "ERROR: unrecognized lock mode: 2139062143". I changed
that to a PANIC and got a core file containing this stack:
frame #4: 0x00000001051e6572 postgres`elog_finish(elevel=22,
fmt="unrecognized lock mode: %d") at elog.c:1365:2
frame #5: 0x0000000104ff033a
postgres`LockAcquireExtended(locktag=0x00007ffeeb14bc28,
lockmode=2139062143, sessionLock=false, dontWait=false,
reportMemoryError=true, locallockp=0x00007ffeeb14bc20) at lock.c:756:3
frame #6: 0x0000000104fedaed postgres`LockRelationOid(relid=16397,
lockmode=2139062143) at lmgr.c:116:8
frame #7: 0x0000000104c056f2
postgres`RangeVarGetRelidExtended(relation=0x00007fbd0f000b58,
lockmode=2139062143, flags=0,
callback=(postgres`RangeVarCallbackForAlterRelation at
tablecmds.c:14834), callback_arg=0x00007fbd0f000d60) at
namespace.c:379:4
frame #8: 0x0000000104d4b14d
postgres`AlterTableLookupRelation(stmt=0x00007fbd0f000d60,
lockmode=2139062143) at tablecmds.c:3445:9
frame #9: 0x000000010501ff8b
postgres`ProcessUtilitySlow(pstate=0x00007fbd10800d18,
pstmt=0x00007fbd0f0010b0, queryString="ALTER INDEX test_idx SET
(int_option = 3);", context=PROCESS_UTILITY_TOPLEVEL,
params=0x0000000000000000, queryEnv=0x0000000000000000,
dest=0x00007fbd0f0011a0, completionTag="") at utility.c:1111:14
frame #10: 0x000000010501f480
postgres`standard_ProcessUtility(pstmt=0x00007fbd0f0010b0,
queryString="ALTER INDEX test_idx SET (int_option = 3);",
context=PROCESS_UTILITY_TOPLEVEL, params=0x0000000000000000,
queryEnv=0x0000000000000000, dest=0x00007fbd0f0011a0,
completionTag="") at utility.c:927:4
AlterTableGetLockLevel() returns that crazy lockmode value, becase it
calls AlterTableGetRelOptionsLockLevel(), I suspect with a garbage
defList, but I didn't dig further.
--
Thomas Munro
https://enterprisedb.com
On Thu, Sep 19, 2019 at 10:51:09AM +1200, Thomas Munro wrote:
Let's see if I can see this on my Mac... yep, with "make -C
src/test/modules/dummy_index_am directory check" I see a similar
failure with "ERROR: unrecognized lock mode: 2139062143". I changed
that to a PANIC and got a core file containing this stack:
A simple make check on the module reproduces the failure, so that's
hard to miss.
From what I can see it is not a problem caused directly by this
module, specifically knowing that this test module is actually copying
what bloom is doing when declaring its reloptions. Take this example:
CREATE EXTENSION bloom;
CREATE TABLE tbloom AS
SELECT
(random() * 1000000)::int as i1,
(random() * 1000000)::int as i2,
(random() * 1000000)::int as i3,
(random() * 1000000)::int as i4,
(random() * 1000000)::int as i5,
(random() * 1000000)::int as i6
FROM
generate_series(1,100);
CREATE INDEX bloomidx ON tbloom USING bloom (i1,i2,i3)
WITH (length=80, col1=2, col2=2, col3=4);
ALTER INDEX bloomidx SET (length=100);
And then you get that:
ERROR: XX000: unrecognized lock mode: 2139062143
LOCATION: LockAcquireExtended, lock.c:756
So the options are registered in the relOpts array managed by
reloptions.c but the data is not properly initialized. Hence when
looking at the lock needed we have an option match, but the lock
number is incorrect, causing the failure. It looks like there is no
direct way to enforce the lockmode used for a reloption added via
add_int_reloption which does the allocation to add the option to
add_reloption(), but enforcing the value to be initialized fixes the
issue:
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -658,6 +658,7 @@ allocate_reloption(bits32 kinds, int type, const
char *name, const char *desc)
newoption->kinds = kinds;
newoption->namelen = strlen(name);
newoption->type = type;
+ newoption->lockmode = AccessExclusiveLock;
MemoryContextSwitchTo(oldcxt);
I would think that initializing that to a sane default is something
that we should do anyway, or is there some trick allowing the
manipulation of relOpts I am missing? Changing the relopts APIs in
back-branches is a no-go of course, but we could improve that for
13~.
While reading through the code, I found some extra issues... Here are
some comments about them.
+++ b/src/test/modules/dummy_index_am/Makefile
@@ -0,0 +1,21 @@
+# contrib/bloom/Makefile
Incorrect copy-paste here.
+extern IndexBulkDeleteResult *dibulkdelete(IndexVacuumInfo *info,
+ IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback,
+ void *callback_state);
All the routines defining the index AM can just be static, so there is
no point to complicate dummy_index.h with most of its contents.
Some routines are missing a (void) in their declaration when the
routines have no arguments. This can cause warnings.
No sure I see the point of the various GUC with the use of WARNING
messages to check the sanity of the parameters. I find that awkward.
--
Michael
В письме от четверг, 19 сентября 2019 г. 17:32:03 MSK пользователь Michael
Paquier написал:
src/test/modules/dummy_index_am directory check" I see a similar
failure with "ERROR: unrecognized lock mode: 2139062143". I changedthat to a PANIC and got a core file containing this stack:
A simple make check on the module reproduces the failure, so that's
hard to miss.
For some reason it does not reproduce on my dev environment, but it not really
important, since the core of the problem is found.
From what I can see it is not a problem caused directly by this
module, specifically knowing that this test module is actually copying
what bloom is doing when declaring its reloptions. Take this example:
CREATE EXTENSION bloom;
CREATE TABLE tbloom AS
SELECT
(random() * 1000000)::int as i1,
(random() * 1000000)::int as i2,
(random() * 1000000)::int as i3,
(random() * 1000000)::int as i4,
(random() * 1000000)::int as i5,
(random() * 1000000)::int as i6
FROM
generate_series(1,100);
CREATE INDEX bloomidx ON tbloom USING bloom (i1,i2,i3)
WITH (length=80, col1=2, col2=2, col3=4);
ALTER INDEX bloomidx SET (length=100);And then you get that:
ERROR: XX000: unrecognized lock mode: 2139062143
LOCATION: LockAcquireExtended, lock.c:756So the options are registered in the relOpts array managed by reloptions.c but the data is not properly initialized. Hence when looking at the lock needed we have an option match, but the lock number is incorrect, causing the failure. It looks like there is no direct way to enforce the lockmode used for a reloption added via add_int_reloption which does the allocation to add the option to add_reloption(), but enforcing the value to be initialized fixes the issue: --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -658,6 +658,7 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc) newoption->kinds = kinds; newoption->namelen = strlen(name); newoption->type = type; + newoption->lockmode = AccessExclusiveLock; MemoryContextSwitchTo(oldcxt);
What a good catch! dummy_index already proved to be useful ;-)
I would think that initializing that to a sane default is something
that we should do anyway, or is there some trick allowing the
manipulation of relOpts I am missing?
Yes I think AccessExclusiveLock is quite good default I think. Especially in
the case when these options are not really used in real world ;-)
Changing the relopts APIs in
back-branches is a no-go of course, but we could improve that for
13~.
As you know I have plans for rewriting options engine and there would be same
options code both for core Access Methods and for options for AM from
extensions. So there would be API for setting lockmode...
But the way it is going right now, I am not sure it will reviewed to reach
13...
PS. Michael, who will submit this lock mode patch? Hope you will do it? It
should go separately from dummy_index for sure...
On Thu, Sep 19, 2019 at 02:13:23PM +0300, Nikolay Shaplov wrote:
What a good catch! dummy_index already proved to be useful ;-)
Yes, the testing around custom reloptions is rather poor now, so this
module has value. I still don't like much its shape though, so I
began hacking on it for integration, and I wanted to get that part
down in this CF :)
There may be other issues, but let's sort out that later if anything
shows up.
Yes I think AccessExclusiveLock is quite good default I think. Especially in
the case when these options are not really used in real world ;-)
I guess so, but with table AMs introduced in 12, I would suspect that
we are going to have much more use cases popping out, and that these
use cases would be very happy to have the possibility to lower the
lock level needed to set a custom reloption. I would like to get that
fixed and back-patched separately. As it is not especially clear for
everybody here in a thread dedicated to a test module that we are
discussing about a backend-side bug, I am going to spawn a new thread
with a proper patch. Perhaps I missed something as well, so it would
be good to get more input on that.
As you know I have plans for rewriting options engine and there would be same
options code both for core Access Methods and for options for AM from
extensions. So there would be API for setting lockmode...
But the way it is going right now, I am not sure it will reviewed to reach
13...
Well, another thing would be to extend the existing routines so as
they take an extra argument to be able to enforce the lockmode, which
is something that can be done without a large rewrite of the whole
facility, and the change is less invasive so it would have better
chances to get into core. I don't mind changing those APIs on HEAD by
the way as long as the breakage involves a clean compilation failure
and I don't think they are the most popular extension APIs ever.
Perhaps others don't have the same line of thoughts, but let's see.
--
Michael
On Fri, Sep 20, 2019 at 09:16:58AM +0900, Michael Paquier wrote:
On Thu, Sep 19, 2019 at 02:13:23PM +0300, Nikolay Shaplov wrote:
What a good catch! dummy_index already proved to be useful ;-)
Yes, the testing around custom reloptions is rather poor now, so this
module has value. I still don't like much its shape though, so I
began hacking on it for integration, and I wanted to get that part
done in this CF :)
So... I have looked at the patch of upthread in details, and as I
suspected the module is over-designed. First, on HEAD the coverage of
reloptions.c is 86.6%, with your patch we get at 94.1%, and with the
attached I reach 95.1% thanks to the addition of a string parameter
with a NULL default value and a NULL description, for roughly half the
code size.
The GUCs are also basically not necessary, as you can just replace the
various WARNING calls (please don't call elog on anything which can be
reached by the user!) by lookups at reloptions in pg_class. Once this
is removed, the whole code gets more simple, and there is no point in
having neither a separate header nor a different set of files and the
size of the whole module gets really reduced.
I still need to do an extra pass on the code (particularly the AM
part), but I think that we could commit that. Please note that I
included the fix for the lockmode I sent today so as the patch can be
tested:
/messages/by-id/20190920013831.GD1844@paquier.xyz
Thoughts?
--
Michael
Attachments:
dummy_index_v3.patchtext/x-diff; charset=us-asciiDownload
diff --git a/contrib/bloom/expected/bloom.out b/contrib/bloom/expected/bloom.out
index 5ab9e34f82..dae12a7d3e 100644
--- a/contrib/bloom/expected/bloom.out
+++ b/contrib/bloom/expected/bloom.out
@@ -5,6 +5,7 @@ CREATE TABLE tst (
);
INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i;
CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (col1 = 3);
+ALTER INDEX bloomidx SET (length=80);
SET enable_seqscan=on;
SET enable_bitmapscan=off;
SET enable_indexscan=off;
diff --git a/contrib/bloom/sql/bloom.sql b/contrib/bloom/sql/bloom.sql
index 32755f2b1a..4733e1e705 100644
--- a/contrib/bloom/sql/bloom.sql
+++ b/contrib/bloom/sql/bloom.sql
@@ -7,6 +7,7 @@ CREATE TABLE tst (
INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i;
CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (col1 = 3);
+ALTER INDEX bloomidx SET (length=80);
SET enable_seqscan=on;
SET enable_bitmapscan=off;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 20f4ed3c38..b59e606771 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -659,6 +659,13 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc)
newoption->namelen = strlen(name);
newoption->type = type;
+ /*
+ * Set the default lock mode for this option. There is no actual way
+ * for a module to enforce it when declaring a custom relation option,
+ * so just use the highest level, which is safe for all cases.
+ */
+ newoption->lockmode = AccessExclusiveLock;
+
MemoryContextSwitchTo(oldcxt);
return newoption;
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 0e4e53d63e..b2eaef3bff 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -7,6 +7,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = \
brin \
commit_ts \
+ dummy_index_am \
dummy_seclabel \
snapshot_too_old \
test_bloomfilter \
diff --git a/src/test/modules/dummy_index_am/.gitignore b/src/test/modules/dummy_index_am/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/dummy_index_am/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/dummy_index_am/Makefile b/src/test/modules/dummy_index_am/Makefile
new file mode 100644
index 0000000000..b7e09b1469
--- /dev/null
+++ b/src/test/modules/dummy_index_am/Makefile
@@ -0,0 +1,20 @@
+# src/test/modules/dummy_index_am/Makefile
+
+MODULES = dummy_index_am
+
+EXTENSION = dummy_index_am
+DATA = dummy_index_am--1.0.sql
+PGFILEDESC = "dummy_index_am - minimized index access method"
+
+REGRESS = reloptions
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/dummy_index_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_index_am/README b/src/test/modules/dummy_index_am/README
new file mode 100644
index 0000000000..5563d060ac
--- /dev/null
+++ b/src/test/modules/dummy_index_am/README
@@ -0,0 +1,11 @@
+Dummy Index AM
+==============
+
+Dummy index is an index module for testing any facility usable by an index
+access method, whose code is kept a maximum simple.
+
+This includes tests for all relation option types:
+- boolean
+- integer
+- real
+- strings
diff --git a/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql b/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql
new file mode 100644
index 0000000000..005863de87
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql
@@ -0,0 +1,19 @@
+/* src/test/modules/dummy_index_am/dummy_index_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_index_am" to load this file. \quit
+
+CREATE FUNCTION dihandler(internal)
+RETURNS index_am_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+-- Access method
+CREATE ACCESS METHOD dummy_index_am TYPE INDEX HANDLER dihandler;
+COMMENT ON ACCESS METHOD dummy_index_am IS 'dummy index access method';
+
+-- Operator classes
+CREATE OPERATOR CLASS int4_ops
+DEFAULT FOR TYPE int4 USING dummy_index_am AS
+ OPERATOR 1 = (int4, int4),
+ FUNCTION 1 hashint4(int4);
diff --git a/src/test/modules/dummy_index_am/dummy_index_am.c b/src/test/modules/dummy_index_am/dummy_index_am.c
new file mode 100644
index 0000000000..0cee0f62a0
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am.c
@@ -0,0 +1,271 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_index.c
+ * Dummy index AM main file.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/dummy_index_am/dummy_index.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/reloptions.h"
+#include "catalog/index.h"
+#include "nodes/pathnodes.h"
+#include "utils/guc.h"
+#include "utils/rel.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/* parse table for fillRelOptions */
+relopt_parse_elt di_relopt_tab[5];
+
+/* Kind of relation options for dummy index */
+relopt_kind di_relopt_kind;
+
+/* Dummy index options */
+typedef struct DummyIndexOptions
+{
+ int32 vl_len_; /* varlena header (do not touch directly!) */
+ int option_int;
+ double option_real;
+ bool option_bool;
+ char *option_string_val;
+ char *option_option_null;
+} DummyIndexOptions;
+
+/*
+ * 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)
+{
+ di_relopt_kind = add_reloption_kind();
+
+ add_int_reloption(di_relopt_kind, "option_int",
+ "Integer option for dummy_index_am",
+ 10, -10, 100);
+ di_relopt_tab[0].optname = "option_int";
+ di_relopt_tab[0].opttype = RELOPT_TYPE_INT;
+ di_relopt_tab[0].offset = offsetof(DummyIndexOptions, option_int);
+
+ add_real_reloption(di_relopt_kind, "option_real",
+ "Real option for dummy_index_am",
+ 3.1415, -10, 100);
+ di_relopt_tab[1].optname = "option_real";
+ di_relopt_tab[1].opttype = RELOPT_TYPE_REAL;
+ di_relopt_tab[1].offset = offsetof(DummyIndexOptions, option_real);
+
+ add_bool_reloption(di_relopt_kind, "option_bool",
+ "Boolean option for dummy_index_am",
+ true);
+ di_relopt_tab[2].optname = "option_bool";
+ di_relopt_tab[2].opttype = RELOPT_TYPE_BOOL;
+ di_relopt_tab[2].offset = offsetof(DummyIndexOptions, option_bool);
+
+ add_string_reloption(di_relopt_kind, "option_string_val",
+ "String option for dummy_index_am with non-NULL default",
+ "DefaultValue", &validate_string_option);
+ di_relopt_tab[3].optname = "option_string_val";
+ di_relopt_tab[3].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[3].offset = offsetof(DummyIndexOptions, option_string_val);
+
+ /*
+ * String option for dummy_index_am with NULL default, and without
+ * description.
+ */
+ add_string_reloption(di_relopt_kind, "option_string_null",
+ NULL, /* description */
+ NULL, &validate_string_option);
+ di_relopt_tab[4].optname = "option_string_null";
+ di_relopt_tab[4].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[4].offset = offsetof(DummyIndexOptions, option_option_null);
+}
+
+
+PG_FUNCTION_INFO_V1(dihandler);
+
+static IndexBuildResult *
+dibuild(Relation heap, Relation index, IndexInfo *indexInfo)
+{
+ IndexBuildResult *result;
+
+ result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
+
+ /* let's pretend that no tuples were scanned */
+ result->heap_tuples = 0;
+ /* and no index tuples were created (that is true) */
+ result->index_tuples = 0;
+
+ return result;
+}
+
+static void
+dibuildempty(Relation index)
+{
+ /* No need to build an init fork for a dummy index */
+}
+
+static bool
+diinsert(Relation index, Datum *values, bool *isnull,
+ ItemPointer ht_ctid, Relation heapRel,
+ IndexUniqueCheck checkUnique,
+ IndexInfo *indexInfo)
+{
+ /* nothing to do */
+ return false;
+}
+
+static IndexBulkDeleteResult *
+dibulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
+ IndexBulkDeleteCallback callback, void *callback_state)
+{
+ /*
+ * There is nothing to delete. Return NULL as there is nothing to
+ * pass to amvacuumcleanup.
+ */
+ return NULL;
+}
+
+static IndexBulkDeleteResult *
+divacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
+{
+ /* Index has not been modified, so returning NULL is fine */
+ return NULL;
+}
+
+static void
+dicostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
+ Cost *indexStartupCost, Cost *indexTotalCost,
+ Selectivity *indexSelectivity, double *indexCorrelation,
+ double *indexPages)
+{
+ /* Tell planner to never use this index! */
+ *indexStartupCost = 1.0e10; /* AKA disable_cost */
+ *indexTotalCost = 1.0e10; /* AKA disable_cost */
+
+ /* Do not care about the rest */
+ *indexSelectivity = 1;
+ *indexCorrelation = 0;
+ *indexPages = 1;
+}
+
+static bytea *
+dioptions(Datum reloptions, bool validate)
+{
+ relopt_value *options;
+ int numoptions;
+ DummyIndexOptions *rdopts;
+
+ /* Parse the user-given reloptions */
+ options = parseRelOptions(reloptions, validate, di_relopt_kind, &numoptions);
+ rdopts = allocateReloptStruct(sizeof(DummyIndexOptions), options, numoptions);
+ fillRelOptions((void *) rdopts, sizeof(DummyIndexOptions), options, numoptions,
+ validate, di_relopt_tab, lengthof(di_relopt_tab));
+
+ return (bytea *) rdopts;
+}
+
+static bool
+divalidate(Oid opclassoid)
+{
+ /* Index is dummy so we are happy with any opclass */
+ return true;
+}
+
+static IndexScanDesc
+dibeginscan(Relation r, int nkeys, int norderbys)
+{
+ IndexScanDesc scan;
+
+ /* Let's pretend we are doing something */
+ scan = RelationGetIndexScan(r, nkeys, norderbys);
+ return scan;
+}
+
+static void
+direscan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
+ ScanKey orderbys, int norderbys)
+{
+ /* nothing to do */
+}
+
+static void
+diendscan(IndexScanDesc scan)
+{
+ /* nothing to do */
+}
+
+/*
+ * Dummy index handler function: return IndexAmRoutine with access method
+ * parameters and callbacks.
+ */
+Datum
+dihandler(PG_FUNCTION_ARGS)
+{
+ IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
+
+ amroutine->amstrategies = 0;
+ amroutine->amsupport = 1;
+ amroutine->amcanorder = false;
+ amroutine->amcanorderbyop = false;
+ amroutine->amcanbackward = false;
+ amroutine->amcanunique = false;
+ amroutine->amcanmulticol = false;
+ amroutine->amoptionalkey = false;
+ amroutine->amsearcharray = false;
+ amroutine->amsearchnulls = false;
+ amroutine->amstorage = false;
+ amroutine->amclusterable = false;
+ amroutine->ampredlocks = false;
+ amroutine->amcanparallel = false;
+ amroutine->amcaninclude = false;
+ amroutine->amkeytype = InvalidOid;
+
+ amroutine->ambuild = dibuild;
+ amroutine->ambuildempty = dibuildempty;
+ amroutine->aminsert = diinsert;
+ amroutine->ambulkdelete = dibulkdelete;
+ amroutine->amvacuumcleanup = divacuumcleanup;
+ amroutine->amcanreturn = NULL;
+ amroutine->amcostestimate = dicostestimate;
+ amroutine->amoptions = dioptions;
+ amroutine->amproperty = NULL;
+ amroutine->amvalidate = divalidate;
+ amroutine->ambeginscan = dibeginscan;
+ amroutine->amrescan = direscan;
+ amroutine->amgettuple = NULL;
+ amroutine->amgetbitmap = NULL;
+ amroutine->amendscan = diendscan;
+ amroutine->ammarkpos = NULL;
+ amroutine->amrestrpos = NULL;
+ amroutine->amestimateparallelscan = NULL;
+ amroutine->aminitparallelscan = NULL;
+ amroutine->amparallelrescan = NULL;
+
+ PG_RETURN_POINTER(amroutine);
+}
+
+void
+_PG_init(void)
+{
+ create_reloptions_table();
+}
diff --git a/src/test/modules/dummy_index_am/dummy_index_am.control b/src/test/modules/dummy_index_am/dummy_index_am.control
new file mode 100644
index 0000000000..7ea62a0c1c
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am.control
@@ -0,0 +1,5 @@
+# dummy_index extension
+comment = 'dummy_index_am - minimized index access method'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_index_am'
+relocatable = true
diff --git a/src/test/modules/dummy_index_am/expected/reloptions.out b/src/test/modules/dummy_index_am/expected/reloptions.out
new file mode 100644
index 0000000000..04a120a0e0
--- /dev/null
+++ b/src/test/modules/dummy_index_am/expected/reloptions.out
@@ -0,0 +1,77 @@
+-- Tests for relation options
+CREATE EXTENSION dummy_index_am;
+CREATE TABLE dummy_test_tab (i int4);
+-- Test with default values.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+--------
+(0 rows)
+
+DROP INDEX dummy_test_idx;
+-- Test with full set of options.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i) WITH (
+ option_bool = false,
+ option_int = 5,
+ option_real = 3.1,
+ option_string_val = NULL,
+ option_string_null = 'val');
+NOTICE: new option value for string parameter null
+NOTICE: new option value for string parameter val
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+------------------------
+ option_bool=false
+ option_int=5
+ option_real=3.1
+ option_string_val=null
+ option_string_null=val
+(5 rows)
+
+-- ALTER INDEX .. SET
+ALTER INDEX dummy_test_idx SET (option_int = 10);
+NOTICE: new option value for string parameter null
+NOTICE: new option value for string parameter val
+ALTER INDEX dummy_test_idx SET (option_bool = true);
+NOTICE: new option value for string parameter null
+NOTICE: new option value for string parameter val
+ALTER INDEX dummy_test_idx SET (option_real = 3.2);
+NOTICE: new option value for string parameter null
+NOTICE: new option value for string parameter val
+ALTER INDEX dummy_test_idx SET (option_string_val = 'val2');
+NOTICE: new option value for string parameter val
+NOTICE: new option value for string parameter val2
+ALTER INDEX dummy_test_idx SET (option_string_null = NULL);
+NOTICE: new option value for string parameter val2
+NOTICE: new option value for string parameter null
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+-------------------------
+ option_int=10
+ option_bool=true
+ option_real=3.2
+ option_string_val=val2
+ option_string_null=null
+(5 rows)
+
+-- ALTER INDEX .. RESET
+ALTER INDEX dummy_test_idx RESET (option_int);
+NOTICE: new option value for string parameter val2
+NOTICE: new option value for string parameter null
+ALTER INDEX dummy_test_idx RESET (option_bool);
+NOTICE: new option value for string parameter val2
+NOTICE: new option value for string parameter null
+ALTER INDEX dummy_test_idx RESET (option_real);
+NOTICE: new option value for string parameter val2
+NOTICE: new option value for string parameter null
+ALTER INDEX dummy_test_idx RESET (option_string_val);
+NOTICE: new option value for string parameter null
+ALTER INDEX dummy_test_idx RESET (option_string_null);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+--------
+(0 rows)
+
+DROP INDEX dummy_test_idx;
diff --git a/src/test/modules/dummy_index_am/sql/reloptions.sql b/src/test/modules/dummy_index_am/sql/reloptions.sql
new file mode 100644
index 0000000000..10ca1a6d03
--- /dev/null
+++ b/src/test/modules/dummy_index_am/sql/reloptions.sql
@@ -0,0 +1,38 @@
+-- Tests for relation options
+CREATE EXTENSION dummy_index_am;
+
+CREATE TABLE dummy_test_tab (i int4);
+
+-- Test with default values.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+DROP INDEX dummy_test_idx;
+
+-- Test with full set of options.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i) WITH (
+ option_bool = false,
+ option_int = 5,
+ option_real = 3.1,
+ option_string_val = NULL,
+ option_string_null = 'val');
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+
+-- ALTER INDEX .. SET
+ALTER INDEX dummy_test_idx SET (option_int = 10);
+ALTER INDEX dummy_test_idx SET (option_bool = true);
+ALTER INDEX dummy_test_idx SET (option_real = 3.2);
+ALTER INDEX dummy_test_idx SET (option_string_val = 'val2');
+ALTER INDEX dummy_test_idx SET (option_string_null = NULL);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+
+-- ALTER INDEX .. RESET
+ALTER INDEX dummy_test_idx RESET (option_int);
+ALTER INDEX dummy_test_idx RESET (option_bool);
+ALTER INDEX dummy_test_idx RESET (option_real);
+ALTER INDEX dummy_test_idx RESET (option_string_val);
+ALTER INDEX dummy_test_idx RESET (option_string_null);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+
+DROP INDEX dummy_test_idx;
On Fri, Sep 20, 2019 at 08:58:27PM +0900, Michael Paquier wrote:
I still need to do an extra pass on the code (particularly the AM
part), but I think that we could commit that. Please note that I
included the fix for the lockmode I sent today so as the patch can be
tested:
/messages/by-id/20190920013831.GD1844@paquier.xyz
I looked at that over the last couple of days, and done as attached.
Well, the actual module is in 0003. I have added more comments to
document the basic AM calls so as it can easier be used as a template
for some other work, and tweaked a couple of things. 0001 and 0002
are just the patches from the other thread to address the issues with
the lock mode of custom reloptions.
--
Michael
Attachments:
v2-0001-Fix-failure-with-lock-mode-used-for-custom-relati.patchtext/x-diff; charset=us-asciiDownload
From c0c2f75ae0b4fa3e6959a1157d400e316f40ada0 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 23 Sep 2019 15:20:37 +0900
Subject: [PATCH v2 1/3] Fix failure with lock mode used for custom relation
options
Relation options can use a custom lock mode since 47167b7, which has
lowered the lock available for some autovacuum parameters, however it
forgot to consider custom relation options. This causes failures with
ALTER TABLE SET when changing a custom relation option, as its lock is
not defined. The existing APIs to define a custom reloption does not
allow to define a custom lock mode, so enforce its initialization to
AccessExclusiveMode which is safe enough in all cases. An upcoming
patch will extend the existing APIs to allow a custom lock mode to be
defined.
The problem can be reproduced with bloom indexes, so add a test there.
Reported-by: Nikolay Sharplov
Analyzed-by: Thomas Munro, Michael Paquier
Author: Michael Paquier
Reviewed-by: Kuntal Ghosh
Discussion: https://postgr.es/m/20190920013831.GD1844@paquier.xyz
Backpatch-through: 9.6
---
contrib/bloom/expected/bloom.out | 1 +
contrib/bloom/sql/bloom.sql | 1 +
src/backend/access/common/reloptions.c | 7 +++++++
3 files changed, 9 insertions(+)
diff --git a/contrib/bloom/expected/bloom.out b/contrib/bloom/expected/bloom.out
index 5ab9e34f82..dae12a7d3e 100644
--- a/contrib/bloom/expected/bloom.out
+++ b/contrib/bloom/expected/bloom.out
@@ -5,6 +5,7 @@ CREATE TABLE tst (
);
INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i;
CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (col1 = 3);
+ALTER INDEX bloomidx SET (length=80);
SET enable_seqscan=on;
SET enable_bitmapscan=off;
SET enable_indexscan=off;
diff --git a/contrib/bloom/sql/bloom.sql b/contrib/bloom/sql/bloom.sql
index 32755f2b1a..4733e1e705 100644
--- a/contrib/bloom/sql/bloom.sql
+++ b/contrib/bloom/sql/bloom.sql
@@ -7,6 +7,7 @@ CREATE TABLE tst (
INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i;
CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (col1 = 3);
+ALTER INDEX bloomidx SET (length=80);
SET enable_seqscan=on;
SET enable_bitmapscan=off;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 20f4ed3c38..b59e606771 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -659,6 +659,13 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc)
newoption->namelen = strlen(name);
newoption->type = type;
+ /*
+ * Set the default lock mode for this option. There is no actual way
+ * for a module to enforce it when declaring a custom relation option,
+ * so just use the highest level, which is safe for all cases.
+ */
+ newoption->lockmode = AccessExclusiveLock;
+
MemoryContextSwitchTo(oldcxt);
return newoption;
--
2.23.0
v2-0002-Allow-definition-of-lock-mode-for-custom-reloptio.patchtext/x-diff; charset=us-asciiDownload
From c495f03450b6c4727b3d2f44b3509916f0ee73ae Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 23 Sep 2019 15:28:02 +0900
Subject: [PATCH v2 2/3] Allow definition of lock mode for custom reloptions
Relation options can define a lock mode other than AccessExclusiveMode
since 47167b7, but modules defining custom relation options did not
really have a way to enforce that. Correct that by extending the
current API set so as modules can define a custom lock mode.
Author: Michael Paquier
Reviewed-by: Kuntal Ghosh
Discussion: https://postgr.es/m/20190920013831.GD1844@paquier.xyz
---
contrib/bloom/blutils.c | 6 ++++--
src/backend/access/common/reloptions.c | 28 +++++++++++---------------
src/include/access/reloptions.h | 11 ++++++----
3 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index cc1670934f..dbb24cb5b2 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -60,7 +60,8 @@ _PG_init(void)
/* Option for length of signature */
add_int_reloption(bl_relopt_kind, "length",
"Length of signature in bits",
- DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH);
+ DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH,
+ AccessExclusiveLock);
bl_relopt_tab[0].optname = "length";
bl_relopt_tab[0].opttype = RELOPT_TYPE_INT;
bl_relopt_tab[0].offset = offsetof(BloomOptions, bloomLength);
@@ -71,7 +72,8 @@ _PG_init(void)
snprintf(buf, sizeof(buf), "col%d", i + 1);
add_int_reloption(bl_relopt_kind, buf,
"Number of bits generated for each index column",
- DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS);
+ DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS,
+ AccessExclusiveLock);
bl_relopt_tab[i + 1].optname = MemoryContextStrdup(TopMemoryContext,
buf);
bl_relopt_tab[i + 1].opttype = RELOPT_TYPE_INT;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index b59e606771..3b8517efea 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -621,7 +621,8 @@ add_reloption(relopt_gen *newoption)
* (for types other than string)
*/
static relopt_gen *
-allocate_reloption(bits32 kinds, int type, const char *name, const char *desc)
+allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
+ LOCKMODE lockmode)
{
MemoryContext oldcxt;
size_t size;
@@ -658,13 +659,7 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc)
newoption->kinds = kinds;
newoption->namelen = strlen(name);
newoption->type = type;
-
- /*
- * Set the default lock mode for this option. There is no actual way
- * for a module to enforce it when declaring a custom relation option,
- * so just use the highest level, which is safe for all cases.
- */
- newoption->lockmode = AccessExclusiveLock;
+ newoption->lockmode = lockmode;
MemoryContextSwitchTo(oldcxt);
@@ -676,12 +671,13 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc)
* Add a new boolean reloption
*/
void
-add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val)
+add_bool_reloption(bits32 kinds, const char *name, const char *desc,
+ bool default_val, LOCKMODE lockmode)
{
relopt_bool *newoption;
newoption = (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL,
- name, desc);
+ name, desc, lockmode);
newoption->default_val = default_val;
add_reloption((relopt_gen *) newoption);
@@ -693,12 +689,12 @@ add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool defaul
*/
void
add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val,
- int min_val, int max_val)
+ int min_val, int max_val, LOCKMODE lockmode)
{
relopt_int *newoption;
newoption = (relopt_int *) allocate_reloption(kinds, RELOPT_TYPE_INT,
- name, desc);
+ name, desc, lockmode);
newoption->default_val = default_val;
newoption->min = min_val;
newoption->max = max_val;
@@ -712,12 +708,12 @@ add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_
*/
void
add_real_reloption(bits32 kinds, const char *name, const char *desc, double default_val,
- double min_val, double max_val)
+ double min_val, double max_val, LOCKMODE lockmode)
{
relopt_real *newoption;
newoption = (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL,
- name, desc);
+ name, desc, lockmode);
newoption->default_val = default_val;
newoption->min = min_val;
newoption->max = max_val;
@@ -736,7 +732,7 @@ add_real_reloption(bits32 kinds, const char *name, const char *desc, double defa
*/
void
add_string_reloption(bits32 kinds, const char *name, const char *desc, const char *default_val,
- validate_string_relopt validator)
+ validate_string_relopt validator, LOCKMODE lockmode)
{
relopt_string *newoption;
@@ -745,7 +741,7 @@ add_string_reloption(bits32 kinds, const char *name, const char *desc, const cha
(validator) (default_val);
newoption = (relopt_string *) allocate_reloption(kinds, RELOPT_TYPE_STRING,
- name, desc);
+ name, desc, lockmode);
newoption->validate_cb = validator;
if (default_val)
{
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 6d392e4d5a..4b82c6370a 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -247,13 +247,16 @@ typedef struct
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
- bool default_val);
+ bool default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
- int default_val, int min_val, int max_val);
+ int default_val, int min_val, int max_val,
+ LOCKMODE lockmode);
extern void add_real_reloption(bits32 kinds, const char *name, const char *desc,
- double default_val, double min_val, double max_val);
+ double default_val, double min_val, double max_val,
+ LOCKMODE lockmode);
extern void add_string_reloption(bits32 kinds, const char *name, const char *desc,
- const char *default_val, validate_string_relopt validator);
+ const char *default_val, validate_string_relopt validator,
+ LOCKMODE lockmode);
extern Datum transformRelOptions(Datum oldOptions, List *defList,
const char *namspace, char *validnsps[],
--
2.23.0
v2-0003-Add-dummy_index_am-to-src-test-modules.patchtext/x-diff; charset=us-asciiDownload
From 2de282f4371786772bfa6bd7a4079e3d3ecadbba Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 24 Sep 2019 11:37:19 +0900
Subject: [PATCH v2 3/3] Add dummy_index_am to src/test/modules/
This includes for now more tests dedicated to reloptions, bringing the
coverage of this code close to 100%, and the module could be used for
other purposes, like a base template for an index AM implementation.
Author: Nikolay Sharplov
Reviewed-by: Dent John, Michael Paquier
Discussion: https://postgr.es/m/17071942.m9zZutALE6@x200m
---
src/test/modules/Makefile | 1 +
src/test/modules/dummy_index_am/.gitignore | 3 +
src/test/modules/dummy_index_am/Makefile | 20 ++
src/test/modules/dummy_index_am/README | 11 +
.../dummy_index_am/dummy_index_am--1.0.sql | 19 ++
.../modules/dummy_index_am/dummy_index_am.c | 309 ++++++++++++++++++
.../dummy_index_am/dummy_index_am.control | 5 +
.../dummy_index_am/expected/reloptions.out | 77 +++++
.../modules/dummy_index_am/sql/reloptions.sql | 38 +++
9 files changed, 483 insertions(+)
create mode 100644 src/test/modules/dummy_index_am/.gitignore
create mode 100644 src/test/modules/dummy_index_am/Makefile
create mode 100644 src/test/modules/dummy_index_am/README
create mode 100644 src/test/modules/dummy_index_am/dummy_index_am--1.0.sql
create mode 100644 src/test/modules/dummy_index_am/dummy_index_am.c
create mode 100644 src/test/modules/dummy_index_am/dummy_index_am.control
create mode 100644 src/test/modules/dummy_index_am/expected/reloptions.out
create mode 100644 src/test/modules/dummy_index_am/sql/reloptions.sql
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 0e4e53d63e..b2eaef3bff 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -7,6 +7,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = \
brin \
commit_ts \
+ dummy_index_am \
dummy_seclabel \
snapshot_too_old \
test_bloomfilter \
diff --git a/src/test/modules/dummy_index_am/.gitignore b/src/test/modules/dummy_index_am/.gitignore
new file mode 100644
index 0000000000..44d119cfcc
--- /dev/null
+++ b/src/test/modules/dummy_index_am/.gitignore
@@ -0,0 +1,3 @@
+# Generated subdirectories
+/log/
+/results/
diff --git a/src/test/modules/dummy_index_am/Makefile b/src/test/modules/dummy_index_am/Makefile
new file mode 100644
index 0000000000..aaf544a385
--- /dev/null
+++ b/src/test/modules/dummy_index_am/Makefile
@@ -0,0 +1,20 @@
+# src/test/modules/dummy_index_am/Makefile
+
+MODULES = dummy_index_am
+
+EXTENSION = dummy_index_am
+DATA = dummy_index_am--1.0.sql
+PGFILEDESC = "dummy_index_am - index 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_index_am
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/dummy_index_am/README b/src/test/modules/dummy_index_am/README
new file mode 100644
index 0000000000..7bcdec56f3
--- /dev/null
+++ b/src/test/modules/dummy_index_am/README
@@ -0,0 +1,11 @@
+Dummy Index AM
+==============
+
+Dummy index AM is a module for testing any facility usable by an index
+access method, whose code is kept a maximum simple.
+
+This includes tests for all relation option types:
+- boolean
+- integer
+- real
+- strings (with and without NULL as default)
diff --git a/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql b/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql
new file mode 100644
index 0000000000..005863de87
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am--1.0.sql
@@ -0,0 +1,19 @@
+/* src/test/modules/dummy_index_am/dummy_index_am--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION dummy_index_am" to load this file. \quit
+
+CREATE FUNCTION dihandler(internal)
+RETURNS index_am_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
+-- Access method
+CREATE ACCESS METHOD dummy_index_am TYPE INDEX HANDLER dihandler;
+COMMENT ON ACCESS METHOD dummy_index_am IS 'dummy index access method';
+
+-- Operator classes
+CREATE OPERATOR CLASS int4_ops
+DEFAULT FOR TYPE int4 USING dummy_index_am AS
+ OPERATOR 1 = (int4, int4),
+ FUNCTION 1 hashint4(int4);
diff --git a/src/test/modules/dummy_index_am/dummy_index_am.c b/src/test/modules/dummy_index_am/dummy_index_am.c
new file mode 100644
index 0000000000..1211907e1d
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am.c
@@ -0,0 +1,309 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_index_am.c
+ * Index AM template main file.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/dummy_index_am/dummy_index_am.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/reloptions.h"
+#include "catalog/index.h"
+#include "nodes/pathnodes.h"
+#include "utils/guc.h"
+#include "utils/rel.h"
+
+PG_MODULE_MAGIC;
+
+void _PG_init(void);
+
+/* parse table for fillRelOptions */
+relopt_parse_elt di_relopt_tab[5];
+
+/* Kind of relation options for dummy index */
+relopt_kind di_relopt_kind;
+
+/* Dummy index options */
+typedef struct DummyIndexOptions
+{
+ int32 vl_len_; /* varlena header (do not touch directly!) */
+ int option_int;
+ double option_real;
+ bool option_bool;
+ char *option_string_val;
+ char *option_option_null;
+} DummyIndexOptions;
+
+/* Handler for index AM */
+PG_FUNCTION_INFO_V1(dihandler);
+
+/*
+ * 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)
+{
+ di_relopt_kind = add_reloption_kind();
+
+ add_int_reloption(di_relopt_kind, "option_int",
+ "Integer option for dummy_index_am",
+ 10, -10, 100, AccessExclusiveLock);
+ di_relopt_tab[0].optname = "option_int";
+ di_relopt_tab[0].opttype = RELOPT_TYPE_INT;
+ di_relopt_tab[0].offset = offsetof(DummyIndexOptions, option_int);
+
+ add_real_reloption(di_relopt_kind, "option_real",
+ "Real option for dummy_index_am",
+ 3.1415, -10, 100, AccessExclusiveLock);
+ di_relopt_tab[1].optname = "option_real";
+ di_relopt_tab[1].opttype = RELOPT_TYPE_REAL;
+ di_relopt_tab[1].offset = offsetof(DummyIndexOptions, option_real);
+
+ add_bool_reloption(di_relopt_kind, "option_bool",
+ "Boolean option for dummy_index_am",
+ true, AccessExclusiveLock);
+ di_relopt_tab[2].optname = "option_bool";
+ di_relopt_tab[2].opttype = RELOPT_TYPE_BOOL;
+ di_relopt_tab[2].offset = offsetof(DummyIndexOptions, option_bool);
+
+ add_string_reloption(di_relopt_kind, "option_string_val",
+ "String option for dummy_index_am with non-NULL default",
+ "DefaultValue", &validate_string_option,
+ AccessExclusiveLock);
+ di_relopt_tab[3].optname = "option_string_val";
+ di_relopt_tab[3].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[3].offset = offsetof(DummyIndexOptions, option_string_val);
+
+ /*
+ * String option for dummy_index_am with NULL default, and without
+ * description.
+ */
+ add_string_reloption(di_relopt_kind, "option_string_null",
+ NULL, /* description */
+ NULL, &validate_string_option,
+ AccessExclusiveLock);
+ di_relopt_tab[4].optname = "option_string_null";
+ di_relopt_tab[4].opttype = RELOPT_TYPE_STRING;
+ di_relopt_tab[4].offset = offsetof(DummyIndexOptions, option_option_null);
+}
+
+
+/*
+ * Build a new index.
+ */
+static IndexBuildResult *
+dibuild(Relation heap, Relation index, IndexInfo *indexInfo)
+{
+ IndexBuildResult *result;
+
+ result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
+
+ /* let's pretend that no tuples were scanned */
+ result->heap_tuples = 0;
+ /* and no index tuples were created (that is true) */
+ result->index_tuples = 0;
+
+ return result;
+}
+
+/*
+ * Build an empty index for the initialiation fork.
+ */
+static void
+dibuildempty(Relation index)
+{
+ /* No need to build an init fork for a dummy index */
+}
+
+/*
+ * Insert new tuple to index AM.
+ */
+static bool
+diinsert(Relation index, Datum *values, bool *isnull,
+ ItemPointer ht_ctid, Relation heapRel,
+ IndexUniqueCheck checkUnique,
+ IndexInfo *indexInfo)
+{
+ /* nothing to do */
+ return false;
+}
+
+/*
+ * Bulk deletion of all index entries pointing to a set of table tuples.
+ */
+static IndexBulkDeleteResult *
+dibulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
+ IndexBulkDeleteCallback callback, void *callback_state)
+{
+ /*
+ * There is nothing to delete. Return NULL as there is nothing to pass to
+ * amvacuumcleanup.
+ */
+ return NULL;
+}
+
+/*
+ * Post-VACUUM cleanup for index AM.
+ */
+static IndexBulkDeleteResult *
+divacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
+{
+ /* Index has not been modified, so returning NULL is fine */
+ return NULL;
+}
+
+/*
+ * Estimate cost of index AM.
+ */
+static void
+dicostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
+ Cost *indexStartupCost, Cost *indexTotalCost,
+ Selectivity *indexSelectivity, double *indexCorrelation,
+ double *indexPages)
+{
+ /* Tell planner to never use this index! */
+ *indexStartupCost = 1.0e10;
+ *indexTotalCost = 1.0e10;
+
+ /* Do not care about the rest */
+ *indexSelectivity = 1;
+ *indexCorrelation = 0;
+ *indexPages = 1;
+}
+
+/*
+ * Parse relation options for index AM, returning a DummyIndexOptions
+ * structure filled with option values.
+ */
+static bytea *
+dioptions(Datum reloptions, bool validate)
+{
+ relopt_value *options;
+ int numoptions;
+ DummyIndexOptions *rdopts;
+
+ /* Parse the user-given reloptions */
+ options = parseRelOptions(reloptions, validate, di_relopt_kind, &numoptions);
+ rdopts = allocateReloptStruct(sizeof(DummyIndexOptions), options, numoptions);
+ fillRelOptions((void *) rdopts, sizeof(DummyIndexOptions), options, numoptions,
+ validate, di_relopt_tab, lengthof(di_relopt_tab));
+
+ return (bytea *) rdopts;
+}
+
+/*
+ * Validator for index AM.
+ */
+static bool
+divalidate(Oid opclassoid)
+{
+ /* Index is dummy so we are happy with any opclass */
+ return true;
+}
+
+/*
+ * Begin scan of index AM.
+ */
+static IndexScanDesc
+dibeginscan(Relation r, int nkeys, int norderbys)
+{
+ IndexScanDesc scan;
+
+ /* Let's pretend we are doing something */
+ scan = RelationGetIndexScan(r, nkeys, norderbys);
+ return scan;
+}
+
+/*
+ * Rescan of index AM.
+ */
+static void
+direscan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
+ ScanKey orderbys, int norderbys)
+{
+ /* nothing to do */
+}
+
+/*
+ * End scan of index AM.
+ */
+static void
+diendscan(IndexScanDesc scan)
+{
+ /* nothing to do */
+}
+
+/*
+ * Index AM handler function: returns IndexAmRoutine with access method
+ * parameters and callbacks.
+ */
+Datum
+dihandler(PG_FUNCTION_ARGS)
+{
+ IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
+
+ amroutine->amstrategies = 0;
+ amroutine->amsupport = 1;
+ amroutine->amcanorder = false;
+ amroutine->amcanorderbyop = false;
+ amroutine->amcanbackward = false;
+ amroutine->amcanunique = false;
+ amroutine->amcanmulticol = false;
+ amroutine->amoptionalkey = false;
+ amroutine->amsearcharray = false;
+ amroutine->amsearchnulls = false;
+ amroutine->amstorage = false;
+ amroutine->amclusterable = false;
+ amroutine->ampredlocks = false;
+ amroutine->amcanparallel = false;
+ amroutine->amcaninclude = false;
+ amroutine->amkeytype = InvalidOid;
+
+ amroutine->ambuild = dibuild;
+ amroutine->ambuildempty = dibuildempty;
+ amroutine->aminsert = diinsert;
+ amroutine->ambulkdelete = dibulkdelete;
+ amroutine->amvacuumcleanup = divacuumcleanup;
+ amroutine->amcanreturn = NULL;
+ amroutine->amcostestimate = dicostestimate;
+ amroutine->amoptions = dioptions;
+ amroutine->amproperty = NULL;
+ amroutine->ambuildphasename = NULL;
+ amroutine->amvalidate = divalidate;
+ amroutine->ambeginscan = dibeginscan;
+ amroutine->amrescan = direscan;
+ amroutine->amgettuple = NULL;
+ amroutine->amgetbitmap = NULL;
+ amroutine->amendscan = diendscan;
+ amroutine->ammarkpos = NULL;
+ amroutine->amrestrpos = NULL;
+ amroutine->amestimateparallelscan = NULL;
+ amroutine->aminitparallelscan = NULL;
+ amroutine->amparallelrescan = NULL;
+
+ PG_RETURN_POINTER(amroutine);
+}
+
+void
+_PG_init(void)
+{
+ create_reloptions_table();
+}
diff --git a/src/test/modules/dummy_index_am/dummy_index_am.control b/src/test/modules/dummy_index_am/dummy_index_am.control
new file mode 100644
index 0000000000..77bdea08ae
--- /dev/null
+++ b/src/test/modules/dummy_index_am/dummy_index_am.control
@@ -0,0 +1,5 @@
+# dummy_index_am extension
+comment = 'dummy_index_am - index access method template'
+default_version = '1.0'
+module_pathname = '$libdir/dummy_index_am'
+relocatable = true
diff --git a/src/test/modules/dummy_index_am/expected/reloptions.out b/src/test/modules/dummy_index_am/expected/reloptions.out
new file mode 100644
index 0000000000..04a120a0e0
--- /dev/null
+++ b/src/test/modules/dummy_index_am/expected/reloptions.out
@@ -0,0 +1,77 @@
+-- Tests for relation options
+CREATE EXTENSION dummy_index_am;
+CREATE TABLE dummy_test_tab (i int4);
+-- Test with default values.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+--------
+(0 rows)
+
+DROP INDEX dummy_test_idx;
+-- Test with full set of options.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i) WITH (
+ option_bool = false,
+ option_int = 5,
+ option_real = 3.1,
+ option_string_val = NULL,
+ option_string_null = 'val');
+NOTICE: new option value for string parameter null
+NOTICE: new option value for string parameter val
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+------------------------
+ option_bool=false
+ option_int=5
+ option_real=3.1
+ option_string_val=null
+ option_string_null=val
+(5 rows)
+
+-- ALTER INDEX .. SET
+ALTER INDEX dummy_test_idx SET (option_int = 10);
+NOTICE: new option value for string parameter null
+NOTICE: new option value for string parameter val
+ALTER INDEX dummy_test_idx SET (option_bool = true);
+NOTICE: new option value for string parameter null
+NOTICE: new option value for string parameter val
+ALTER INDEX dummy_test_idx SET (option_real = 3.2);
+NOTICE: new option value for string parameter null
+NOTICE: new option value for string parameter val
+ALTER INDEX dummy_test_idx SET (option_string_val = 'val2');
+NOTICE: new option value for string parameter val
+NOTICE: new option value for string parameter val2
+ALTER INDEX dummy_test_idx SET (option_string_null = NULL);
+NOTICE: new option value for string parameter val2
+NOTICE: new option value for string parameter null
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+-------------------------
+ option_int=10
+ option_bool=true
+ option_real=3.2
+ option_string_val=val2
+ option_string_null=null
+(5 rows)
+
+-- ALTER INDEX .. RESET
+ALTER INDEX dummy_test_idx RESET (option_int);
+NOTICE: new option value for string parameter val2
+NOTICE: new option value for string parameter null
+ALTER INDEX dummy_test_idx RESET (option_bool);
+NOTICE: new option value for string parameter val2
+NOTICE: new option value for string parameter null
+ALTER INDEX dummy_test_idx RESET (option_real);
+NOTICE: new option value for string parameter val2
+NOTICE: new option value for string parameter null
+ALTER INDEX dummy_test_idx RESET (option_string_val);
+NOTICE: new option value for string parameter null
+ALTER INDEX dummy_test_idx RESET (option_string_null);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+ unnest
+--------
+(0 rows)
+
+DROP INDEX dummy_test_idx;
diff --git a/src/test/modules/dummy_index_am/sql/reloptions.sql b/src/test/modules/dummy_index_am/sql/reloptions.sql
new file mode 100644
index 0000000000..10ca1a6d03
--- /dev/null
+++ b/src/test/modules/dummy_index_am/sql/reloptions.sql
@@ -0,0 +1,38 @@
+-- Tests for relation options
+CREATE EXTENSION dummy_index_am;
+
+CREATE TABLE dummy_test_tab (i int4);
+
+-- Test with default values.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+DROP INDEX dummy_test_idx;
+
+-- Test with full set of options.
+CREATE INDEX dummy_test_idx ON dummy_test_tab
+ USING dummy_index_am (i) WITH (
+ option_bool = false,
+ option_int = 5,
+ option_real = 3.1,
+ option_string_val = NULL,
+ option_string_null = 'val');
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+
+-- ALTER INDEX .. SET
+ALTER INDEX dummy_test_idx SET (option_int = 10);
+ALTER INDEX dummy_test_idx SET (option_bool = true);
+ALTER INDEX dummy_test_idx SET (option_real = 3.2);
+ALTER INDEX dummy_test_idx SET (option_string_val = 'val2');
+ALTER INDEX dummy_test_idx SET (option_string_null = NULL);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+
+-- ALTER INDEX .. RESET
+ALTER INDEX dummy_test_idx RESET (option_int);
+ALTER INDEX dummy_test_idx RESET (option_bool);
+ALTER INDEX dummy_test_idx RESET (option_real);
+ALTER INDEX dummy_test_idx RESET (option_string_val);
+ALTER INDEX dummy_test_idx RESET (option_string_null);
+SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx';
+
+DROP INDEX dummy_test_idx;
--
2.23.0
On 2019-Sep-24, Michael Paquier wrote:
I looked at that over the last couple of days, and done as attached.
Well, the actual module is in 0003. I have added more comments to
document the basic AM calls so as it can easier be used as a template
for some other work, and tweaked a couple of things. 0001 and 0002
are just the patches from the other thread to address the issues with
the lock mode of custom reloptions.
0003 looks useful, thanks for completing it. I think it would be a good
idea to test invalid values for each type of reloption too (passing
floating point to integers, strings to floating point, and so on).
If you can get this pushed, I'll push the enum reloptions on top.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
В Fri, 20 Sep 2019 20:58:27 +0900
Michael Paquier <michael@paquier.xyz> пишет:
Sorry I am really slow to answer... I hope it is not too late.
The GUCs are also basically not necessary, as you can just replace the
various WARNING calls (please don't call elog on anything which can be
reached by the user!) by lookups at reloptions in pg_class. Once this
is removed, the whole code gets more simple, and there is no point in
having neither a separate header nor a different set of files and the
size of the whole module gets really reduced.
Reading options from pg_class is not a good idea. We already do this in
reloption regression test. Here the thing is almost same...
My point of testing was to read these values from bytea right from
inside of the module. This is not exactly the same value as in pg_class.
It _should_ be the same. But nobody promised is _is_ the same. That is
why I was reading it right from relotions in-memory bytea, the same way
real access methods do it.
And then we came to a GUC variables. Because it we have five reloptions
and we print them all each time we change something, there would be
quite huge output.
It is ok when everything goes well. Comparing with 'expected' is cheap.
But is something goes wrong, then it would be very difficult to find
proper place in this output to deal with it.
So I created GUCs so we can get only one output in a row, not a whole
bunch.
These are my points.
В письме от вторник, 24 сентября 2019 г. 9:25:54 MSK пользователь Alvaro
Herrera написал:
0003 looks useful, thanks for completing it. I think it would be a good
idea to test invalid values for each type of reloption too (passing
floating point to integers, strings to floating point, and so on).
We already do it in reloption regression tests.
My idea was to test here only the things that can't be tested in regression
tests, on in real indexes like bloom.
On 2019-Sep-24, Nikolay Shaplov wrote:
В письме от вторник, 24 сентября 2019 г. 9:25:54 MSK пользователь Alvaro
Herrera написал:0003 looks useful, thanks for completing it. I think it would be a good
idea to test invalid values for each type of reloption too (passing
floating point to integers, strings to floating point, and so on).We already do it in reloption regression tests.
My idea was to test here only the things that can't be tested in regression
tests, on in real indexes like bloom.
I suppose that makes sense. But of course when I push enum reloptions
I will have to add such a test, since bloom does not have one.
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Sep 24, 2019 at 04:49:11PM +0300, Nikolay Shaplov wrote:
And then we came to a GUC variables. Because it we have five reloptions
and we print them all each time we change something, there would be
quite huge output.
Well, that depends on how you design your tests. The first versions
of the patch overdid it, and those GUCs have IMO little place for a
module aimed as well to be a minimized referential template focused on
testing some portions of the backend code.
It is ok when everything goes well. Comparing with 'expected' is cheap.
But is something goes wrong, then it would be very difficult to find
proper place in this output to deal with it.
So I created GUCs so we can get only one output in a row, not a whole
bunch.
I am still not convinced that this is worth the complication. Your
point is that you want to make *on-demand* and *user-visible* the set
of options stored in rd_options after filling in the relation options
using the static table used in the AM.
One way to do that could be to have a simple wrapper function which
could be called at SQL level to do those checks, or you could issue a
NOTICE with all the data filled in amoptions() or even ambuild(),
though the former makes the most sense as we fill in the options
there.
One thing that I think would value in the module would be to show how
a custom string option can be properly parsed when doing some
decisions in the AM. Now we store an offset in the static table, and
one needs to do a small dance with it to fetch the actual option
value.
This can be guessed easily as for example gist has a string option
with "buffering", but we could document that better in the dummy
template, say like that:
@@ -206,6 +210,15 @@ dioptions(Datum reloptions, bool validate)
fillRelOptions((void *) rdopts, sizeof(DummyIndexOptions), options, numoptions,
validate, di_relopt_tab, lengthof(di_relopt_tab));
+ option_string_val = (char *) rdopts + rdopts->option_string_val_offset;
+ option_string_null = (char *) rdopts + rdopts->option_string_null_offset;
+ ereport(NOTICE,
+ (errmsg("table option_int %d, option_real %f, option_bool %s, "
+ "option_string_val %s, option_option_null %s",
+ rdopts->option_int, rdopts->option_real,
+ rdopts->option_bool ? "true" : "false",
+ option_string_val ? option_string_val : "NULL",
+ option_string_null ? option_string_null : "NULL")));
The patch I have in my hands now is already doing a lot, so I am
discarding that part for now. And we can easily improve it
incrementally.
(One extra thing which is also annoying with the current interface is
that we don't actually pass down the option name within the validator
function for string options like GUCs, so you cannot know on which
option you work on if a module generates logs, I'll send an extra
patch for that on a separate thread.)
--
Michael
On Tue, Sep 24, 2019 at 12:38:30PM -0300, Alvaro Herrera wrote:
On 2019-Sep-24, Nikolay Shaplov wrote:
В письме от вторник, 24 сентября 2019 г. 9:25:54 MSK пользователь Alvaro
Herrera написал:0003 looks useful, thanks for completing it. I think it would be a good
idea to test invalid values for each type of reloption too (passing
floating point to integers, strings to floating point, and so on).We already do it in reloption regression tests.
My idea was to test here only the things that can't be tested in regression
tests, on in real indexes like bloom.I suppose that makes sense. But of course when I push enum reloptions
I will have to add such a test, since bloom does not have one.
Good point. We rely now on the GUC parsing for reloptions, so having
cross-checks about what patterns are allowed or not is a good idea for
all reloption types. I have added all that, and committed the
module. The amount of noise generated by the string validator routine
was a bit annoying, so I have silenced them where they don't really
matter (basically everything except the initial creation).
--
Michael