From 2718f015e44354dca5ba99927d6ef0aa2dbb3b1f Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Mon, 10 Nov 2025 22:18:28 -0800 Subject: [PATCH 6/6] Add regression test module for custom COPY format. --- src/test/modules/Makefile | 1 + src/test/modules/meson.build | 1 + .../test_copy_custom_format/.gitignore | 3 + .../modules/test_copy_custom_format/Makefile | 21 ++ .../expected/test_copy_custom_format.out | 97 ++++++++ .../test_copy_custom_format/meson.build | 35 +++ .../sql/test_copy_custom_format.sql | 34 +++ .../test_copy_custom_format.c | 217 ++++++++++++++++++ .../test_copy_custom_format.conf | 1 + 9 files changed, 410 insertions(+) create mode 100644 src/test/modules/test_copy_custom_format/.gitignore create mode 100644 src/test/modules/test_copy_custom_format/Makefile create mode 100644 src/test/modules/test_copy_custom_format/expected/test_copy_custom_format.out create mode 100644 src/test/modules/test_copy_custom_format/meson.build create mode 100644 src/test/modules/test_copy_custom_format/sql/test_copy_custom_format.sql create mode 100644 src/test/modules/test_copy_custom_format/test_copy_custom_format.c create mode 100644 src/test/modules/test_copy_custom_format/test_copy_custom_format.conf diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 902a7954101..710ccb74990 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -19,6 +19,7 @@ SUBDIRS = \ test_bitmapset \ test_bloomfilter \ test_copy_callbacks \ + test_copy_custom_format \ test_custom_rmgrs \ test_ddl_deparse \ test_dsa \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 14fc761c4cf..5a2e4e03b80 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -18,6 +18,7 @@ subdir('test_binaryheap') subdir('test_bitmapset') subdir('test_bloomfilter') subdir('test_copy_callbacks') +subdir('test_copy_custom_format') subdir('test_custom_rmgrs') subdir('test_ddl_deparse') subdir('test_dsa') diff --git a/src/test/modules/test_copy_custom_format/.gitignore b/src/test/modules/test_copy_custom_format/.gitignore new file mode 100644 index 00000000000..8d3af519eb1 --- /dev/null +++ b/src/test/modules/test_copy_custom_format/.gitignore @@ -0,0 +1,3 @@ +/log/ +/results/ +/tmp_check/ \ No newline at end of file diff --git a/src/test/modules/test_copy_custom_format/Makefile b/src/test/modules/test_copy_custom_format/Makefile new file mode 100644 index 00000000000..8ad1cebaba6 --- /dev/null +++ b/src/test/modules/test_copy_custom_format/Makefile @@ -0,0 +1,21 @@ +# src/test/modules/test_copy_custom_format/Makefile + +MODULE_big = test_copy_custom_format +OBJS = \ + $(WIN32RES) \ + test_copy_custom_format.o +PGFILEDESC = "test_copy_custom_format - test custom COPY FORMAT" + +REGRESS = test_copy_custom_format +REGRESS_OPTS = --temp-config $(top_srcdir)/src/test/modules/test_copy_custom_format/test_copy_custom_format.conf + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_copy_custom_format +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_copy_custom_format/expected/test_copy_custom_format.out b/src/test/modules/test_copy_custom_format/expected/test_copy_custom_format.out new file mode 100644 index 00000000000..a91ac293447 --- /dev/null +++ b/src/test/modules/test_copy_custom_format/expected/test_copy_custom_format.out @@ -0,0 +1,97 @@ +CREATE TABLE copy_data (a smallint, b integer, c bigint); +INSERT INTO copy_data VALUES (1, 2, 3), (12, 34, 56), (123, 456, 789); +COPY copy_data FROM stdin WITH (FORMAT 'test_format'); +NOTICE: CopyFromInFunc: attribute: smallint +NOTICE: CopyFromInFunc: attribute: integer +NOTICE: CopyFromInFunc: attribute: bigint +NOTICE: CopyFromStart: the number of attributes: 3 +NOTICE: common_int 0 common_bool 0 from_str "(null)" +NOTICE: CopyFromOneRow +NOTICE: CopyFromEnd +COPY copy_data TO stdout WITH (FORMAT 'test_format'); +NOTICE: CopyToOutFunc: attribute: smallint +NOTICE: CopyToOutFunc: attribute: integer +NOTICE: CopyToOutFunc: attribute: bigint +NOTICE: CopyToStart: the number of attributes: 3 +NOTICE: common_int 0 common_bool 0 to_str "(null)" +NOTICE: CopyToOneRow: the number of valid values: 3 +NOTICE: CopyToOneRow: the number of valid values: 3 +NOTICE: CopyToOneRow: the number of valid values: 3 +NOTICE: CopyToEnd +-- Error +COPY copy_data FROM stdin WITH (FORMAT 'error'); +ERROR: COPY format "error" not recognized +LINE 1: COPY copy_data FROM stdin WITH (FORMAT 'error'); + ^ +COPY copy_data FROM stdin WITH (FORMAT 'text', FORMAT 'error'); +ERROR: conflicting or redundant options +LINE 1: COPY copy_data FROM stdin WITH (FORMAT 'text', FORMAT 'error... + ^ +-- Option handling, COPY FROM +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_int 10); +NOTICE: CopyFromInFunc: attribute: smallint +NOTICE: CopyFromInFunc: attribute: integer +NOTICE: CopyFromInFunc: attribute: bigint +NOTICE: CopyFromStart: the number of attributes: 3 +NOTICE: common_int 10 common_bool 0 from_str "(null)" +NOTICE: CopyFromOneRow +NOTICE: CopyFromEnd +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_int -10); +NOTICE: CopyFromInFunc: attribute: smallint +NOTICE: CopyFromInFunc: attribute: integer +NOTICE: CopyFromInFunc: attribute: bigint +NOTICE: CopyFromStart: the number of attributes: 3 +NOTICE: common_int -10 common_bool 0 from_str "(null)" +NOTICE: CopyFromOneRow +NOTICE: CopyFromEnd +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_int 'a'); -- error +ERROR: common_int requires an integer value +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_bool 'true'); +NOTICE: CopyFromInFunc: attribute: smallint +NOTICE: CopyFromInFunc: attribute: integer +NOTICE: CopyFromInFunc: attribute: bigint +NOTICE: CopyFromStart: the number of attributes: 3 +NOTICE: common_int 0 common_bool 1 from_str "(null)" +NOTICE: CopyFromOneRow +NOTICE: CopyFromEnd +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_bool 'false'); +NOTICE: CopyFromInFunc: attribute: smallint +NOTICE: CopyFromInFunc: attribute: integer +NOTICE: CopyFromInFunc: attribute: bigint +NOTICE: CopyFromStart: the number of attributes: 3 +NOTICE: common_int 0 common_bool 0 from_str "(null)" +NOTICE: CopyFromOneRow +NOTICE: CopyFromEnd +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_bool 'hello'); -- error +ERROR: common_bool requires a Boolean value +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_int 100, common_bool false, from_str 'from option'); +NOTICE: CopyFromInFunc: attribute: smallint +NOTICE: CopyFromInFunc: attribute: integer +NOTICE: CopyFromInFunc: attribute: bigint +NOTICE: CopyFromStart: the number of attributes: 3 +NOTICE: common_int 100 common_bool 0 from_str "from option" +NOTICE: CopyFromOneRow +NOTICE: CopyFromEnd +COPY copy_data FROM stdin WITH (FORMAT 'test_format', invalid 'option'); -- error +ERROR: COPY format "invalid" not recognized +LINE 1: ... copy_data FROM stdin WITH (FORMAT 'test_format', invalid 'o... + ^ +COPY copy_data FROM stdin WITH (FORMAT 'test_format', format 'text'); -- error +ERROR: conflicting or redundant options +LINE 1: ... copy_data FROM stdin WITH (FORMAT 'test_format', format 'te... + ^ +-- Option handling, COPY FROM +COPY copy_data TO stdout WITH (FORMAT 'test_format', common_int -10, common_bool false, to_str 'to option'); +NOTICE: CopyToOutFunc: attribute: smallint +NOTICE: CopyToOutFunc: attribute: integer +NOTICE: CopyToOutFunc: attribute: bigint +NOTICE: CopyToStart: the number of attributes: 3 +NOTICE: common_int -10 common_bool 0 to_str "to option" +NOTICE: CopyToOneRow: the number of valid values: 3 +NOTICE: CopyToOneRow: the number of valid values: 3 +NOTICE: CopyToOneRow: the number of valid values: 3 +NOTICE: CopyToEnd +COPY copy_data TO stdout WITH (FORMAT 'test_format', from_str 'to option'); -- error +ERROR: COPY format "from_str" not recognized +LINE 1: ...Y copy_data TO stdout WITH (FORMAT 'test_format', from_str '... + ^ diff --git a/src/test/modules/test_copy_custom_format/meson.build b/src/test/modules/test_copy_custom_format/meson.build new file mode 100644 index 00000000000..6cc6c8b60fd --- /dev/null +++ b/src/test/modules/test_copy_custom_format/meson.build @@ -0,0 +1,35 @@ +# Copyright (c) 2025, PostgreSQL Global Development Group + +test_copy_custom_format_sources = files( +'test_copy_custom_format.c', +) + +if host_system == 'windows' + test_copy_custom_format_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_copy_custom_format', + '--FILEDESC', 'test_copy_custom_format - test custom COPY FORMAT',]) +endif + +test_copy_custom_format = shared_module('test_copy_custom_format', + test_copy_custom_format_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += test_copy_custom_format + +tests += { + 'name': 'test_copy_custom_format', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_copy_custom_format', + ], + 'regress_args': [ + '--temp-config', files('test_copy_custom_format.conf'), + ], + # Disabled because these tests require + # "shared_preload_libraries=test_custom_copy_format", which typical + # runningcheck users do not have (e.g. buildfarm clients). + 'runningcheck': false, + }, +} diff --git a/src/test/modules/test_copy_custom_format/sql/test_copy_custom_format.sql b/src/test/modules/test_copy_custom_format/sql/test_copy_custom_format.sql new file mode 100644 index 00000000000..b6564f0355b --- /dev/null +++ b/src/test/modules/test_copy_custom_format/sql/test_copy_custom_format.sql @@ -0,0 +1,34 @@ +CREATE TABLE copy_data (a smallint, b integer, c bigint); +INSERT INTO copy_data VALUES (1, 2, 3), (12, 34, 56), (123, 456, 789); + +COPY copy_data FROM stdin WITH (FORMAT 'test_format'); +\. +COPY copy_data TO stdout WITH (FORMAT 'test_format'); + +-- Error +COPY copy_data FROM stdin WITH (FORMAT 'error'); +COPY copy_data FROM stdin WITH (FORMAT 'text', FORMAT 'error'); + + +-- Option handling, COPY FROM +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_int 10); +\. +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_int -10); +\. +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_int 'a'); -- error + +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_bool 'true'); +\. +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_bool 'false'); +\. +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_bool 'hello'); -- error + +COPY copy_data FROM stdin WITH (FORMAT 'test_format', common_int 100, common_bool false, from_str 'from option'); +\. + +COPY copy_data FROM stdin WITH (FORMAT 'test_format', invalid 'option'); -- error +COPY copy_data FROM stdin WITH (FORMAT 'test_format', format 'text'); -- error + +-- Option handling, COPY FROM +COPY copy_data TO stdout WITH (FORMAT 'test_format', common_int -10, common_bool false, to_str 'to option'); +COPY copy_data TO stdout WITH (FORMAT 'test_format', from_str 'to option'); -- error diff --git a/src/test/modules/test_copy_custom_format/test_copy_custom_format.c b/src/test/modules/test_copy_custom_format/test_copy_custom_format.c new file mode 100644 index 00000000000..a63390e875b --- /dev/null +++ b/src/test/modules/test_copy_custom_format/test_copy_custom_format.c @@ -0,0 +1,217 @@ +/*-------------------------------------------------------------------------- + * + * test_copy_custom_format.c + * Code for testing custom COPY format. + * + * Portions Copyright (c) 2025, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_copy_custom_format/test_copy_custom_format.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "commands/copystate.h" +#include "commands/copyapi.h" +#include "commands/defrem.h" +#include "utils/builtins.h" + +PG_MODULE_MAGIC; + +typedef struct TestCopyCommonOption +{ + int common_int; + bool common_bool; +} TestCopyCommonOption; + +typedef struct TestCopyFromState +{ + CopyFromStateData base; + + TestCopyCommonOption common_opts; + char *from_str_option; +} TestCopyFromState; + +typedef struct TestCopyToState +{ + CopyToStateData base; + + TestCopyCommonOption common_opts; + char *to_str_option; +} TestCopyToState; + +static Size +TestCopyToEsimateSpace(void) +{ + return sizeof(TestCopyToState); +} + +static Size +TestCopyFromEsimateSpace(void) +{ + return sizeof(TestCopyFromState); +} + +static bool +TestCopyProcessCommonOption(TestCopyCommonOption * opt, DefElem *option) +{ + if (strcmp(option->defname, "common_int") == 0) + { + int val = defGetInt32(option); + + opt->common_int = val; + + return true; + } + else if (strcmp(option->defname, "common_bool") == 0) + { + bool val = defGetBoolean(option); + + opt->common_bool = val; + + return true; + } + + return false; +} + +static bool +TestCopyFromProcessOneOption(CopyFromState ccstate, DefElem *option) +{ + TestCopyFromState *cstate = (TestCopyFromState *) ccstate; + + if (TestCopyProcessCommonOption(&cstate->common_opts, option)) + { + return true; + } + else if (strcmp(option->defname, "from_str") == 0) + { + char *val = defGetString(option); + + cstate->from_str_option = val; + return true; + } + + return false; +} + +static bool +TestCopyToProcessOneOption(CopyToState ccstate, DefElem *option) +{ + TestCopyToState *cstate = (TestCopyToState *) ccstate; + + if (TestCopyProcessCommonOption(&cstate->common_opts, option)) + { + return true; + } + else if (strcmp(option->defname, "to_str") == 0) + { + char *val = defGetString(option); + + cstate->to_str_option = val; + return true; + } + + return false; +} + +static void +TestCopyFromInFunc(CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam) +{ + ereport(NOTICE, + errmsg("CopyFromInFunc: attribute: %s", format_type_be(atttypid))); +} + +static void +TestCopyFromStart(CopyFromState ccstate, TupleDesc tupDesc) +{ + TestCopyFromState *cstate = (TestCopyFromState *) ccstate; + + ereport(NOTICE, + errmsg("CopyFromStart: the number of attributes: %d", tupDesc->natts)); + ereport(NOTICE, + errmsg("common_int %d common_bool %d from_str \"%s\"", + cstate->common_opts.common_int, + cstate->common_opts.common_bool, + cstate->from_str_option)); +} + +static bool +TestCopyFromOneRow(CopyFromState cstate, ExprContext *econtext, Datum *values, bool *nulls, + CopyFromRowInfo * rowinfo) +{ + ereport(NOTICE, + errmsg("CopyFromOneRow")); + + return false; +} + +static void +TestCopyFromEnd(CopyFromState cstate) +{ + ereport(NOTICE, + errmsg("CopyFromEnd")); +} + +static void +TestCopyToOutFunc(CopyToState cstate, Oid atttypid, FmgrInfo *finfo) +{ + ereport(NOTICE, + errmsg("CopyToOutFunc: attribute: %s", format_type_be(atttypid))); +} + +static void +TestCopyToStart(CopyToState ccstate, TupleDesc tupDesc) +{ + TestCopyToState *cstate = (TestCopyToState *) ccstate; + + ereport(NOTICE, + errmsg("CopyToStart: the number of attributes: %d", tupDesc->natts)); + ereport(NOTICE, + errmsg("common_int %d common_bool %d to_str \"%s\"", + cstate->common_opts.common_int, + cstate->common_opts.common_bool, + cstate->to_str_option)); +} + +static void +TestCopyToOneRow(CopyToState cstate, TupleTableSlot *slot) +{ + ereport(NOTICE, (errmsg("CopyToOneRow: the number of valid values: %u", slot->tts_nvalid))); +} + +static void +TestCopyToEnd(CopyToState cstate) +{ + ereport(NOTICE, (errmsg("CopyToEnd"))); +} + +static const CopyToRoutine CopyToRoutineTestCopyFormat = { + .CopyToEstimateStateSpace = TestCopyToEsimateSpace, + .CopyToProcessOneOption = TestCopyToProcessOneOption, + .CopyToOutFunc = TestCopyToOutFunc, + .CopyToStart = TestCopyToStart, + .CopyToOneRow = TestCopyToOneRow, + .CopyToEnd = TestCopyToEnd, +}; + + +static const CopyFromRoutine CopyFromRoutineTestCopyFormat = { + .CopyFromEstimateStateSpace = TestCopyFromEsimateSpace, + .CopyFromProcessOneOption = TestCopyFromProcessOneOption, + .CopyFromInFunc = TestCopyFromInFunc, + .CopyFromStart = TestCopyFromStart, + .CopyFromOneRow = TestCopyFromOneRow, + .CopyFromEnd = TestCopyFromEnd, +}; + +void +_PG_init(void) +{ + RegisterCopyCustomFormat("test_format", + &CopyFromRoutineTestCopyFormat, + &CopyToRoutineTestCopyFormat); +} diff --git a/src/test/modules/test_copy_custom_format/test_copy_custom_format.conf b/src/test/modules/test_copy_custom_format/test_copy_custom_format.conf new file mode 100644 index 00000000000..fb7cf22a3b1 --- /dev/null +++ b/src/test/modules/test_copy_custom_format/test_copy_custom_format.conf @@ -0,0 +1 @@ +shared_preload_libraries = 'test_copy_custom_format' -- 2.47.3