From 640c19869f8c4b5c34d3982b5e1cd40e62abbb85 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 25 Sep 2019 12:11:12 +0900 Subject: [PATCH] Add dummy_index_am to src/test/modules/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This includes more tests dedicated to relation options, bringing the coverage of this code close to 100%, and the module can be used for other purposes, like a base template for an index AM implementation. Author: Nikolay Sharplov, Michael Paquier Reviewed-by: Álvaro Herrera, Dent John 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 | 312 ++++++++++++++++++ .../dummy_index_am/dummy_index_am.control | 5 + .../dummy_index_am/expected/reloptions.out | 115 +++++++ .../modules/dummy_index_am/sql/reloptions.sql | 68 ++++ src/tools/pgindent/typedefs.list | 1 + 10 files changed, 555 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..59a918bd61 --- /dev/null +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -0,0 +1,312 @@ +/*------------------------------------------------------------------------- + * + * dummy_index_am.c + * Index AM template main file. + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * 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; + int option_string_val_offset; + int option_string_null_offset; +} 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_offset); + + /* + * 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_string_null_offset); +} + + +/* + * 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..c19e79a7c4 --- /dev/null +++ b/src/test/modules/dummy_index_am/expected/reloptions.out @@ -0,0 +1,115 @@ +-- 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) + +-- Silence validation checks for strings +SET client_min_messages TO 'warning'; +-- 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'; + 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); +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'; + unnest +-------- +(0 rows) + +-- Cross-type checks for reloption values +-- Integer +ALTER INDEX dummy_test_idx SET (option_int = 3.3); -- ok +ALTER INDEX dummy_test_idx SET (option_int = true); -- error +ERROR: invalid value for integer option "option_int": true +ALTER INDEX dummy_test_idx SET (option_int = 'val3'); -- error +ERROR: invalid value for integer option "option_int": val3 +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx'; + unnest +---------------- + option_int=3.3 +(1 row) + +ALTER INDEX dummy_test_idx RESET (option_int); +-- Boolean +ALTER INDEX dummy_test_idx SET (option_bool = 4); -- error +ERROR: invalid value for boolean option "option_bool": 4 +ALTER INDEX dummy_test_idx SET (option_bool = 1); -- ok, as true +ALTER INDEX dummy_test_idx SET (option_bool = 3.4); -- error +ERROR: invalid value for boolean option "option_bool": 3.4 +ALTER INDEX dummy_test_idx SET (option_bool = 'val4'); -- error +ERROR: invalid value for boolean option "option_bool": val4 +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx'; + unnest +--------------- + option_bool=1 +(1 row) + +ALTER INDEX dummy_test_idx RESET (option_bool); +-- Float +ALTER INDEX dummy_test_idx SET (option_real = 4); -- ok +ALTER INDEX dummy_test_idx SET (option_real = true); -- error +ERROR: invalid value for floating point option "option_real": true +ALTER INDEX dummy_test_idx SET (option_real = 'val5'); -- error +ERROR: invalid value for floating point option "option_real": val5 +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx'; + unnest +--------------- + option_real=4 +(1 row) + +ALTER INDEX dummy_test_idx RESET (option_real); +-- String +ALTER INDEX dummy_test_idx SET (option_string_val = 4); -- ok +ALTER INDEX dummy_test_idx SET (option_string_val = 3.5); -- ok +ALTER INDEX dummy_test_idx SET (option_string_val = true); -- ok, as "true" +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx'; + unnest +------------------------ + option_string_val=true +(1 row) + +ALTER INDEX dummy_test_idx RESET (option_string_val); +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..004a196128 --- /dev/null +++ b/src/test/modules/dummy_index_am/sql/reloptions.sql @@ -0,0 +1,68 @@ +-- 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'; + +-- Silence validation checks for strings +SET client_min_messages TO 'warning'; + +-- 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'; + +-- Cross-type checks for reloption values +-- Integer +ALTER INDEX dummy_test_idx SET (option_int = 3.3); -- ok +ALTER INDEX dummy_test_idx SET (option_int = true); -- error +ALTER INDEX dummy_test_idx SET (option_int = 'val3'); -- error +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx'; +ALTER INDEX dummy_test_idx RESET (option_int); +-- Boolean +ALTER INDEX dummy_test_idx SET (option_bool = 4); -- error +ALTER INDEX dummy_test_idx SET (option_bool = 1); -- ok, as true +ALTER INDEX dummy_test_idx SET (option_bool = 3.4); -- error +ALTER INDEX dummy_test_idx SET (option_bool = 'val4'); -- error +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx'; +ALTER INDEX dummy_test_idx RESET (option_bool); +-- Float +ALTER INDEX dummy_test_idx SET (option_real = 4); -- ok +ALTER INDEX dummy_test_idx SET (option_real = true); -- error +ALTER INDEX dummy_test_idx SET (option_real = 'val5'); -- error +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx'; +ALTER INDEX dummy_test_idx RESET (option_real); +-- String +ALTER INDEX dummy_test_idx SET (option_string_val = 4); -- ok +ALTER INDEX dummy_test_idx SET (option_string_val = 3.5); -- ok +ALTER INDEX dummy_test_idx SET (option_string_val = true); -- ok, as "true" +SELECT unnest(reloptions) FROM pg_class WHERE relname = 'dummy_test_idx'; +ALTER INDEX dummy_test_idx RESET (option_string_val); + +DROP INDEX dummy_test_idx; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index fb65265175..60c76cb160 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -535,6 +535,7 @@ DropSubscriptionStmt DropTableSpaceStmt DropUserMappingStmt DropdbStmt +DummyIndexOptions DumpComponents DumpId DumpOptions