agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v3] Identity columns 75+ messages / 6 participants [nested] [flat]
* [PATCH v3] Identity columns @ 2016-12-31 17:00 Peter Eisentraut <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Peter Eisentraut @ 2016-12-31 17:00 UTC (permalink / raw) This is the SQL standard-conforming variant of PostgreSQL's serial columns. It also fixes a few usability issues that serial columns have: - CREATE TABLE / LIKE copies default but refers to same sequence - cannot add/drop serialness with ALTER TABLE - dropping default does not drop sequence - slight weirdnesses because serial is some kind of special macro --- doc/src/sgml/catalogs.sgml | 11 + doc/src/sgml/information_schema.sgml | 11 +- doc/src/sgml/ref/alter_table.sgml | 29 ++ doc/src/sgml/ref/create_table.sgml | 58 +++- doc/src/sgml/ref/insert.sgml | 41 +++ src/backend/access/common/tupdesc.c | 5 + src/backend/catalog/genbki.pl | 2 + src/backend/catalog/heap.c | 15 +- src/backend/catalog/index.c | 1 + src/backend/catalog/information_schema.sql | 19 +- src/backend/catalog/pg_depend.c | 17 +- src/backend/catalog/sql_features.txt | 12 +- src/backend/commands/sequence.c | 108 ++++++- src/backend/commands/tablecmds.c | 287 ++++++++++++++++- src/backend/nodes/copyfuncs.c | 4 + src/backend/nodes/equalfuncs.c | 4 + src/backend/nodes/makefuncs.c | 1 + src/backend/nodes/outfuncs.c | 9 + src/backend/nodes/readfuncs.c | 1 + src/backend/parser/analyze.c | 2 + src/backend/parser/gram.y | 126 +++++++- src/backend/parser/parse_utilcmd.c | 396 ++++++++++++++++++------ src/backend/rewrite/rewriteHandler.c | 43 ++- src/backend/utils/adt/ruleutils.c | 8 + src/backend/utils/cache/lsyscache.c | 21 ++ src/backend/utils/cache/relcache.c | 1 + src/backend/utils/errcodes.txt | 1 + src/bin/pg_dump/pg_dump.c | 87 +++++- src/bin/pg_dump/pg_dump.h | 3 + src/bin/psql/describe.c | 26 +- src/bin/psql/tab-complete.c | 18 +- src/include/catalog/catversion.h | 2 +- src/include/catalog/dependency.h | 2 +- src/include/catalog/pg_attribute.h | 20 +- src/include/catalog/pg_class.h | 2 +- src/include/commands/sequence.h | 1 + src/include/nodes/parsenodes.h | 28 +- src/include/parser/kwlist.h | 2 + src/include/utils/lsyscache.h | 1 + src/test/regress/expected/create_table_like.out | 47 +++ src/test/regress/expected/identity.out | 274 ++++++++++++++++ src/test/regress/expected/truncate.out | 30 ++ src/test/regress/parallel_schedule | 5 + src/test/regress/serial_schedule | 1 + src/test/regress/sql/create_table_like.sql | 14 + src/test/regress/sql/identity.sql | 162 ++++++++++ src/test/regress/sql/truncate.sql | 18 ++ 47 files changed, 1800 insertions(+), 176 deletions(-) create mode 100644 src/test/regress/expected/identity.out create mode 100644 src/test/regress/sql/identity.sql diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 493050618d..3c18fffc1c 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1105,6 +1105,17 @@ <title><structname>pg_attribute</> Columns</title> </row> <row> + <entry><structfield>attidentity</structfield></entry> + <entry><type>char</type></entry> + <entry></entry> + <entry> + If a space character, then not an identity column. Otherwise, + <literal>a</literal> = generated always, <literal>d</literal> = + generated by default. + </entry> + </row> + + <row> <entry><structfield>attisdropped</structfield></entry> <entry><type>bool</type></entry> <entry></entry> diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml index c43e325d06..8ece4398ba 100644 --- a/doc/src/sgml/information_schema.sgml +++ b/doc/src/sgml/information_schema.sgml @@ -1583,13 +1583,20 @@ <title><literal>columns</literal> Columns</title> <row> <entry><literal>is_identity</literal></entry> <entry><type>yes_or_no</type></entry> - <entry>Applies to a feature not available in <productname>PostgreSQL</></entry> + <entry> + If the column is an identity column, then <literal>YES</literal>, + else <literal>NO</literal>. + </entry> </row> <row> <entry><literal>identity_generation</literal></entry> <entry><type>character_data</type></entry> - <entry>Applies to a feature not available in <productname>PostgreSQL</></entry> + <entry> + If the column is an identity column, then <literal>ALWAYS</literal> + or <literal>BY DEFAULT</literal>, reflecting the definition of the + column. + </entry> </row> <row> diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index da431f8369..be1c7f33a9 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -46,6 +46,9 @@ ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable> ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> DROP DEFAULT ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> { SET | DROP } NOT NULL + ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] + ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> { SET GENERATED { ALWAYS | BY DEFAULT } | SET <replaceable>sequence_option</replaceable> | RESET } [...] + ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> DROP IDENTITY ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable> ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] ) ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] ) @@ -180,6 +183,32 @@ <title>Description</title> </varlistentry> <varlistentry> + <term><literal>ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY</literal></term> + <term><literal>SET GENERATED { ALWAYS | BY DEFAULT }</literal></term> + <term><literal>DROP IDENTITY</literal></term> + <listitem> + <para> + These forms change whether a column is an identity column or change the + generation attribute of an existing identity column. + See <xref linkend="sql-createtable"> for details. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>SET <replaceable>sequence_option</replaceable></literal></term> + <term><literal>RESET</literal></term> + <listitem> + <para> + These forms alter the sequence that underlies an existing identity + column. <replaceable>sequence_option</replaceable> is an option + supported by <xref linkend="sql-altersequence"> such + as <literal>INCREMENT BY</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><literal>SET STATISTICS</literal></term> <listitem> <para> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 58f8bf6d6a..1d4b6db189 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -62,6 +62,7 @@ NULL | CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] | DEFAULT <replaceable>default_expr</replaceable> | + GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] | UNIQUE <replaceable class="PARAMETER">index_parameters</replaceable> | PRIMARY KEY <replaceable class="PARAMETER">index_parameters</replaceable> | REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] @@ -81,7 +82,7 @@ <phrase>and <replaceable class="PARAMETER">like_option</replaceable> is:</phrase> -{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } +{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | IDENTITY | INDEXES | STORAGE | COMMENTS | ALL } <phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase> @@ -442,6 +443,12 @@ <title>Parameters</title> the original and new tables. </para> <para> + Any identity specifications of copied column definitions will only be + copied if <literal>INCLUDING IDENTITY</literal> is specified. A new + sequence is created for each identity column of the new table, separate + from the sequences associated with the old table. + </para> + <para> Not-null constraints are always copied to the new table. <literal>CHECK</literal> constraints will be copied only if <literal>INCLUDING CONSTRAINTS</literal> is specified. @@ -473,7 +480,7 @@ <title>Parameters</title> </para> <para> <literal>INCLUDING ALL</literal> is an abbreviated form of - <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>. + <literal>INCLUDING DEFAULTS INCLUDING IDENTITY INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>. </para> <para> Note that unlike <literal>INHERITS</literal>, columns and @@ -588,6 +595,35 @@ <title>Parameters</title> </varlistentry> <varlistentry> + <term><literal>GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ]</literal></term> + <listitem> + <para> + This clause creates the column as an <firstterm>identity + column</firstterm>. It will have an implicit sequence attached to it + and the column in new rows will automatically have values from the + sequence assigned to it. + </para> + + <para> + The clauses <literal>ALWAYS</literal> and <literal>BY DEFAULT</literal> + determine how the sequence value is given precedence over a + user-specified value in an <command>INSERT</command> statement. + If <literal>ALWAYS</literal> is specified, a user-specified value is + only accepted if the <command>INSERT</command> statement + specifies <literal>OVERRIDING SYSTEM VALUE</literal>. If <literal>BY + DEFAULT</literal> is specified, then the user-specified value takes + precedence. See <xref linkend="sql-insert"> for details. + </para> + + <para> + The optional <replaceable>sequence_options</replaceable> clause can be + used to override the options of the sequence. + See <xref linkend="sql-createsequence"> for details. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><literal>UNIQUE</> (column constraint)</term> <term><literal>UNIQUE ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] )</> (table constraint)</term> @@ -1220,7 +1256,7 @@ <title>Notes</title> <para> Using OIDs in new applications is not recommended: where - possible, using a <literal>SERIAL</literal> or other sequence + possible, using an identity column or other sequence generator as the table's primary key is preferred. However, if your application does make use of OIDs to identify specific rows of a table, it is recommended to create a unique constraint @@ -1280,7 +1316,7 @@ <title>Examples</title> ); CREATE TABLE distributors ( - did integer PRIMARY KEY DEFAULT nextval('serial'), + did integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, name varchar(40) NOT NULL CHECK (name <> '') ); </programlisting> @@ -1664,6 +1700,20 @@ <title>Zero-column Tables</title> </refsect2> <refsect2> + <title>Multiple Identity Columns</title> + + <para> + <productname>PostgreSQL</productname> allows a table to have more than one + identity column. The standard specifies that a table can have at most one + identity column. This is relaxed mainly to give more flexibility for + doing schema changes or migrations. Note that + the <command>INSERT</command> command supports only one override clause + that applies to the entire statement, so having multiple identity columns + with different behaviors is not well supported. + </para> + </refsect2> + + <refsect2> <title><literal>LIKE</> Clause</title> <para> diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml index 9339826818..4cef7e92b8 100644 --- a/doc/src/sgml/ref/insert.sgml +++ b/doc/src/sgml/ref/insert.sgml @@ -23,6 +23,7 @@ <synopsis> [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ] INSERT INTO <replaceable class="PARAMETER">table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ] [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] + [ OVERRIDING { SYSTEM | USER} VALUE ] { DEFAULT VALUES | VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="PARAMETER">query</replaceable> } [ ON CONFLICT [ <replaceable class="parameter">conflict_target</replaceable> ] <replaceable class="parameter">conflict_action</replaceable> ] [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ] @@ -202,10 +203,43 @@ <title id="sql-inserting-params-title">Inserting</title> </varlistentry> <varlistentry> + <term><literal>OVERRIDING SYSTEM VALUE</literal></term> + <listitem> + <para> + Without this clause, it is an error to specify an explicit value + (other than <literal>DEFAULT</literal>) for an identity column defined + as <literal>GENERATED ALWAYS</literal>. This clause overrides that + restriction. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>OVERRIDING USER VALUE</literal></term> + <listitem> + <para> + If this clause is specified, then any values supplied for identity + columns defined as <literal>GENERATED BY DEFAULT</literal> are ignored + and the default sequence-generated values are applied. + </para> + + <para> + This clause is useful for example when copying values between tables. + Writing <literal>INSERT INTO tbl2 OVERRIDING USER VALUE SELECT * FROM + tbl1</literal> will copy from <literal>tbl1</literal> all columns that + are not identity columns in <literal>tbl2</literal> but will continue + the sequence counters for any identity columns. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><literal>DEFAULT VALUES</literal></term> <listitem> <para> All columns will be filled with their default values. + (An <literal>OVERRIDING</literal> clause is not permitted in this + form.) </para> </listitem> </varlistentry> @@ -711,6 +745,13 @@ <title>Compatibility</title> </para> <para> + The SQL standard specifies that <literal>OVERRIDING SYSTEM VALUE</literal> + can only be specified if an identity column that is generated always + exists. PostgreSQL allows the clause in any case and ignores it if it is + not applicable. + </para> + + <para> Possible limitations of the <replaceable class="PARAMETER">query</replaceable> clause are documented under <xref linkend="sql-select">. diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index b56d0e336f..8a8244ec42 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -149,6 +149,7 @@ CreateTupleDescCopy(TupleDesc tupdesc) memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE); desc->attrs[i]->attnotnull = false; desc->attrs[i]->atthasdef = false; + desc->attrs[i]->attidentity = ' '; } desc->tdtypeid = tupdesc->tdtypeid; @@ -256,6 +257,7 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno, /* since we're not copying constraints or defaults, clear these */ dst->attrs[dstAttno - 1]->attnotnull = false; dst->attrs[dstAttno - 1]->atthasdef = false; + dst->attrs[dstAttno - 1]->attidentity = ' '; } /* @@ -400,6 +402,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (attr1->atthasdef != attr2->atthasdef) return false; + if (attr1->attidentity != attr2->attidentity) + return false; if (attr1->attisdropped != attr2->attisdropped) return false; if (attr1->attislocal != attr2->attislocal) @@ -533,6 +537,7 @@ TupleDescInitEntry(TupleDesc desc, att->attnotnull = false; att->atthasdef = false; + att->attidentity = ' '; att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl index 26d165203d..f0d79c324a 100644 --- a/src/backend/catalog/genbki.pl +++ b/src/backend/catalog/genbki.pl @@ -409,6 +409,7 @@ sub emit_pgattr_row attcacheoff => '-1', atttypmod => '-1', atthasdef => 'f', + attidentity => "' '", attisdropped => 'f', attislocal => 't', attinhcount => '0', @@ -425,6 +426,7 @@ sub bki_insert my @attnames = @_; my $oid = $row->{oid} ? "OID = $row->{oid} " : ''; my $bki_values = join ' ', map $row->{$_}, @attnames; + $bki_values =~ s/'/"/g; printf BKI "insert %s( %s)\n", $oid, $bki_values; } diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index e5d6aecc3f..c0f66425c1 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -142,37 +142,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum); static FormData_pg_attribute a1 = { 0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData), SelfItemPointerAttributeNumber, 0, -1, -1, - false, 'p', 's', true, false, false, true, 0 + false, 'p', 's', true, false, ' ', false, true, 0 }; static FormData_pg_attribute a2 = { 0, {"oid"}, OIDOID, 0, sizeof(Oid), ObjectIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, ' ', false, true, 0 }; static FormData_pg_attribute a3 = { 0, {"xmin"}, XIDOID, 0, sizeof(TransactionId), MinTransactionIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, ' ', false, true, 0 }; static FormData_pg_attribute a4 = { 0, {"cmin"}, CIDOID, 0, sizeof(CommandId), MinCommandIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, ' ', false, true, 0 }; static FormData_pg_attribute a5 = { 0, {"xmax"}, XIDOID, 0, sizeof(TransactionId), MaxTransactionIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, ' ', false, true, 0 }; static FormData_pg_attribute a6 = { 0, {"cmax"}, CIDOID, 0, sizeof(CommandId), MaxCommandIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, ' ', false, true, 0 }; /* @@ -184,7 +184,7 @@ static FormData_pg_attribute a6 = { static FormData_pg_attribute a7 = { 0, {"tableoid"}, OIDOID, 0, sizeof(Oid), TableOidAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, ' ', false, true, 0 }; static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7}; @@ -628,6 +628,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign); values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull); values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef); + values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity); values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped); values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal); values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount); diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 08b0989112..eca570eb49 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -353,6 +353,7 @@ ConstructTupleDescriptor(Relation heapRelation, to->attcacheoff = -1; to->attnotnull = false; to->atthasdef = false; + to->attidentity = ' '; to->attislocal = true; to->attinhcount = 0; to->attcollation = collationObjectId[i]; diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index 182d2d0674..8d86cafc6c 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -646,7 +646,7 @@ CREATE VIEW columns AS CAST(c.relname AS sql_identifier) AS table_name, CAST(a.attname AS sql_identifier) AS column_name, CAST(a.attnum AS cardinal_number) AS ordinal_position, - CAST(pg_get_expr(ad.adbin, ad.adrelid) AS character_data) AS column_default, + CAST(CASE WHEN a.atthasdef AND a.attidentity = ' ' THEN pg_get_expr(ad.adbin, ad.adrelid) END AS character_data) AS column_default, CAST(CASE WHEN a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) THEN 'NO' ELSE 'YES' END AS yes_or_no) AS is_nullable, @@ -727,13 +727,13 @@ CREATE VIEW columns AS CAST(a.attnum AS sql_identifier) AS dtd_identifier, CAST('NO' AS yes_or_no) AS is_self_referencing, - CAST('NO' AS yes_or_no) AS is_identity, - CAST(null AS character_data) AS identity_generation, - CAST(null AS character_data) AS identity_start, - CAST(null AS character_data) AS identity_increment, - CAST(null AS character_data) AS identity_maximum, - CAST(null AS character_data) AS identity_minimum, - CAST(null AS yes_or_no) AS identity_cycle, + CAST(CASE WHEN a.attidentity IN ('a', 'd') THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_identity, + CAST(CASE a.attidentity WHEN 'a' THEN 'ALWAYS' WHEN 'd' THEN 'BY DEFAULT' END AS character_data) AS identity_generation, + CAST(p.start_value AS character_data) AS identity_start, + CAST(p.increment AS character_data) AS identity_increment, + CAST(p.maximum_value AS character_data) AS identity_maximum, + CAST(p.minimum_value AS character_data) AS identity_minimum, + CAST(CASE WHEN p.cycle_option THEN 'YES' ELSE 'NO' END AS yes_or_no) AS identity_cycle, CAST('NEVER' AS character_data) AS is_generated, CAST(null AS character_data) AS generation_expression, @@ -750,6 +750,8 @@ CREATE VIEW columns AS ON (t.typtype = 'd' AND t.typbasetype = bt.oid) LEFT JOIN (pg_collation co JOIN pg_namespace nco ON (co.collnamespace = nco.oid)) ON a.attcollation = co.oid AND (nco.nspname, co.collname) <> ('pg_catalog', 'default') + LEFT JOIN (pg_depend dep JOIN pg_class seq ON (dep.classid = 'pg_class'::regclass AND dep.objid = seq.oid AND dep.deptype = 'i') JOIN LATERAL pg_sequence_parameters(seq.oid) p ON true) + ON (dep.refclassid = 'pg_attrdef'::regclass AND dep.refobjid = ad.oid) WHERE (NOT pg_is_other_temp_schema(nc.oid)) @@ -1543,6 +1545,7 @@ CREATE VIEW sequences AS FROM pg_namespace nc, pg_class c, pg_sequence s WHERE c.relnamespace = nc.oid AND c.relkind = 'S' + AND NOT EXISTS (SELECT 1 FROM pg_depend WHERE classid = 'pg_class'::regclass AND objid = c.oid AND deptype = 'i') AND (NOT pg_is_other_temp_schema(nc.oid)) AND c.oid = s.seqrelid AND (pg_has_role(c.relowner, 'USAGE') diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c index 7a0713e6cc..0823b882f9 100644 --- a/src/backend/catalog/pg_depend.c +++ b/src/backend/catalog/pg_depend.c @@ -19,6 +19,7 @@ #include "access/htup_details.h" #include "catalog/dependency.h" #include "catalog/indexing.h" +#include "catalog/pg_attrdef.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_extension.h" @@ -554,17 +555,20 @@ markSequenceUnowned(Oid seqId) { deleteDependencyRecordsForClass(RelationRelationId, seqId, RelationRelationId, DEPENDENCY_AUTO); + deleteDependencyRecordsForClass(RelationRelationId, seqId, + AttrDefaultRelationId, DEPENDENCY_INTERNAL); } /* - * Collect a list of OIDs of all sequences owned by the specified relation. + * Collect a list of OIDs of all sequences owned by the specified relation, + * and column if specified. */ List * -getOwnedSequences(Oid relid) +getOwnedSequences(Oid relid, AttrNumber attnum) { List *result = NIL; Relation depRel; - ScanKeyData key[2]; + ScanKeyData key[3]; SysScanDesc scan; HeapTuple tup; @@ -578,9 +582,14 @@ getOwnedSequences(Oid relid) Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid)); + if (attnum) + ScanKeyInit(&key[2], + Anum_pg_depend_refobjsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(attnum)); scan = systable_beginscan(depRel, DependReferenceIndexId, true, - NULL, 2, key); + NULL, attnum ? 3 : 2, key); while (HeapTupleIsValid(tup = systable_getnext(scan))) { diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index 8956ba9304..2821b9b702 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -200,7 +200,7 @@ F181 Multiple module support NO F191 Referential delete actions YES F200 TRUNCATE TABLE statement YES F201 CAST function YES -F202 TRUNCATE TABLE: identity column restart option NO +F202 TRUNCATE TABLE: identity column restart option YES F221 Explicit defaults YES F222 INSERT statement: DEFAULT VALUES clause YES F231 Privilege tables YES @@ -241,9 +241,9 @@ F381 Extended schema manipulation 02 ALTER TABLE statement: ADD CONSTRAINT claus F381 Extended schema manipulation 03 ALTER TABLE statement: DROP CONSTRAINT clause YES F382 Alter column data type YES F383 Set column not null clause YES -F384 Drop identity property clause NO +F384 Drop identity property clause YES F385 Drop column generation expression clause NO -F386 Set identity column generation clause NO +F386 Set identity column generation clause YES F391 Long identifiers YES F392 Unicode escapes in identifiers YES F393 Unicode escapes in literals YES @@ -420,11 +420,11 @@ T152 DISTINCT predicate with negation YES T171 LIKE clause in table definition YES T172 AS subquery clause in table definition YES T173 Extended LIKE clause in table definition YES -T174 Identity columns NO +T174 Identity columns YES T175 Generated columns NO T176 Sequence generator support NO -T177 Sequence generator support: simple restart option NO -T178 Identity columns: simple restart option NO +T177 Sequence generator support: simple restart option YES +T178 Identity columns: simple restart option YES T180 System-versioned tables NO T181 Application-time period tables NO T191 Referential action RESTRICT YES diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 668d82771a..f5e6a4f6e4 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -25,6 +25,7 @@ #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" +#include "catalog/pg_attrdef.h" #include "catalog/pg_sequence.h" #include "catalog/pg_type.h" #include "commands/defrem.h" @@ -38,6 +39,7 @@ #include "storage/smgr.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/resowner.h" #include "utils/syscache.h" @@ -98,9 +100,10 @@ static Form_pg_sequence_data read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple); static void init_params(ParseState *pstate, List *options, bool isInit, Form_pg_sequence seqform, - Form_pg_sequence_data seqdataform, List **owned_by); + Form_pg_sequence_data seqdataform, List **owned_by, + DependencyType *deptype); static void do_setval(Oid relid, int64 next, bool iscalled); -static void process_owned_by(Relation seqrel, List *owned_by); +static void process_owned_by(Relation seqrel, List *owned_by, DependencyType deptype); /* @@ -113,6 +116,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) FormData_pg_sequence seqform; FormData_pg_sequence_data seqdataform; List *owned_by; + DependencyType deptype; CreateStmt *stmt = makeNode(CreateStmt); Oid seqoid; ObjectAddress address; @@ -150,7 +154,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) } /* Check and set all option values */ - init_params(pstate, seq->options, true, &seqform, &seqdataform, &owned_by); + init_params(pstate, seq->options, true, &seqform, &seqdataform, &owned_by, &deptype); /* * Create relation (and fill value[] and null[] for the tuple) @@ -167,6 +171,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->storage = 0; coldef->raw_default = NULL; coldef->cooked_default = NULL; + coldef->identity = ' '; coldef->collClause = NULL; coldef->collOid = InvalidOid; coldef->constraints = NIL; @@ -216,7 +221,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* process OWNED BY if given */ if (owned_by) - process_owned_by(rel, owned_by); + process_owned_by(rel, owned_by, deptype); heap_close(rel, NoLock); @@ -415,6 +420,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) Form_pg_sequence_data seqdata; FormData_pg_sequence_data newseqdata; List *owned_by; + DependencyType deptype; ObjectAddress address; Relation rel; HeapTuple tuple; @@ -452,7 +458,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform = (Form_pg_sequence) GETSTRUCT(tuple); /* Check and set new values */ - init_params(pstate, stmt->options, false, seqform, &newseqdata, &owned_by); + init_params(pstate, stmt->options, false, seqform, &newseqdata, &owned_by, &deptype); /* Clear local cache so that we don't think we have cached numbers */ /* Note that we do not change the currval() state */ @@ -495,7 +501,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) /* process OWNED BY if given */ if (owned_by) - process_owned_by(seqrel, owned_by); + process_owned_by(seqrel, owned_by, deptype); InvokeObjectPostAlterHook(RelationRelationId, relid, 0); @@ -1219,7 +1225,8 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) static void init_params(ParseState *pstate, List *options, bool isInit, Form_pg_sequence seqform, - Form_pg_sequence_data seqdataform, List **owned_by) + Form_pg_sequence_data seqdataform, List **owned_by, + DependencyType *deptype) { DefElem *start_value = NULL; DefElem *restart_value = NULL; @@ -1228,9 +1235,11 @@ init_params(ParseState *pstate, List *options, bool isInit, DefElem *min_value = NULL; DefElem *cache_value = NULL; DefElem *is_cycled = NULL; + DefElem *deptype_el = NULL; ListCell *option; *owned_by = NIL; + *deptype = DEPENDENCY_AUTO; foreach(option, options) { @@ -1308,6 +1317,15 @@ init_params(ParseState *pstate, List *options, bool isInit, parser_errposition(pstate, defel->location))); *owned_by = defGetQualifiedName(defel); } + else if (strcmp(defel->defname, "deptype") == 0) + { + if (deptype_el) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + deptype_el = defel; + *deptype = intVal(deptype_el->arg); + } else elog(ERROR, "option \"%s\" not recognized", defel->defname); @@ -1486,6 +1504,38 @@ init_params(ParseState *pstate, List *options, bool isInit, seqform->seqcache = 1; } +static Oid +get_attrdef_oid(Oid relid, AttrNumber attnum) +{ + Relation attrdef_rel; + ScanKeyData scankeys[2]; + SysScanDesc scan; + HeapTuple tuple; + Oid result = InvalidOid; + + attrdef_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock); + + ScanKeyInit(&scankeys[0], + Anum_pg_attrdef_adrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + ScanKeyInit(&scankeys[1], + Anum_pg_attrdef_adnum, + BTEqualStrategyNumber, F_INT2EQ, + Int16GetDatum(attnum)); + + scan = systable_beginscan(attrdef_rel, AttrDefaultIndexId, true, + NULL, 2, scankeys); + + if (HeapTupleIsValid(tuple = systable_getnext(scan))) + result = HeapTupleGetOid(tuple); + + systable_endscan(scan); + heap_close(attrdef_rel, RowExclusiveLock); + + return result; +} + /* * Process an OWNED BY option for CREATE/ALTER SEQUENCE * @@ -1495,7 +1545,7 @@ init_params(ParseState *pstate, List *options, bool isInit, * as the sequence. */ static void -process_owned_by(Relation seqrel, List *owned_by) +process_owned_by(Relation seqrel, List *owned_by, DependencyType deptype) { int nnames; Relation tablerel; @@ -1531,6 +1581,7 @@ process_owned_by(Relation seqrel, List *owned_by) /* Must be a regular or foreign table */ if (!(tablerel->rd_rel->relkind == RELKIND_RELATION || tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || + tablerel->rd_rel->relkind == RELKIND_VIEW || tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -1574,6 +1625,20 @@ process_owned_by(Relation seqrel, List *owned_by) depobject.objectId = RelationGetRelid(seqrel); depobject.objectSubId = 0; recordDependencyOn(&depobject, &refobject, DEPENDENCY_AUTO); + + /* + * For identity columns, also record an internal dependency of the + * sequence on the default (you drop the default, the sequence is + * removed). We still do the auto dependency on the column, because + * that is what TRUNCATE RESTART IDENTITY looks for. + */ + if (deptype == DEPENDENCY_INTERNAL) + { + refobject.classId = AttrDefaultRelationId; + refobject.objectId = get_attrdef_oid(RelationGetRelid(tablerel), attnum); + refobject.objectSubId = 0; + recordDependencyOn(&depobject, &refobject, DEPENDENCY_INTERNAL); + } } /* Done, but hold lock until commit */ @@ -1583,6 +1648,33 @@ process_owned_by(Relation seqrel, List *owned_by) /* + * Return sequence parameters in a list of the form created by the parser. + */ +List * +sequence_options(Oid relid) +{ + HeapTuple pgstuple; + Form_pg_sequence pgsform; + List *options = NIL; + + pgstuple = SearchSysCache1(SEQRELID, relid); + if (!HeapTupleIsValid(pgstuple)) + elog(ERROR, "cache lookup failed for sequence %u", relid); + pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple); + + options = lappend(options, makeDefElem("cache", (Node *) makeInteger(pgsform->seqcache), -1)); + options = lappend(options, makeDefElem("cycle", (Node *) makeInteger(pgsform->seqcycle), -1)); + options = lappend(options, makeDefElem("increment", (Node *) makeInteger(pgsform->seqincrement), -1)); + options = lappend(options, makeDefElem("maxvalue", (Node *) makeInteger(pgsform->seqmax), -1)); + options = lappend(options, makeDefElem("minvalue", (Node *) makeInteger(pgsform->seqmin), -1)); + options = lappend(options, makeDefElem("start", (Node *) makeInteger(pgsform->seqstart), -1)); + + ReleaseSysCache(pgstuple); + + return options; +} + +/* * Return sequence parameters (formerly for use by information schema) */ Datum diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2f605ce83d..bec6e6ffa2 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -358,6 +358,11 @@ static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, const char *colName, LOCKMODE lockmode); static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName, Node *newDefault, LOCKMODE lockmode); +static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName, + Node *def, LOCKMODE lockmode); +static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName, + Node *def, LOCKMODE lockmode); +static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, LOCKMODE lockmode); static void ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode); static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, @@ -698,6 +703,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, cookedDefaults = lappend(cookedDefaults, cooked); descriptor->attrs[attnum - 1]->atthasdef = true; } + + if (colDef->identity) + descriptor->attrs[attnum - 1]->attidentity = colDef->identity; } /* @@ -1283,7 +1291,7 @@ ExecuteTruncate(TruncateStmt *stmt) foreach(cell, rels) { Relation rel = (Relation) lfirst(cell); - List *seqlist = getOwnedSequences(RelationGetRelid(rel)); + List *seqlist = getOwnedSequences(RelationGetRelid(rel), 0); ListCell *seqcell; foreach(seqcell, seqlist) @@ -1804,6 +1812,15 @@ MergeAttributes(List *schema, List *supers, char relpersistence, continue; /* leave newattno entry as zero */ /* + * XXX This could be done, but it needs more thought on what it + * means. + */ + if (attribute->attidentity != ' ') + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot inherit from table with identity column"))); + + /* * Does it conflict with some previously inherited column? */ exist_attno = findAttrByName(attributeName, inhSchema); @@ -1877,6 +1894,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def->storage = attribute->attstorage; def->raw_default = NULL; def->cooked_default = NULL; + def->identity = attribute->attidentity; def->collClause = NULL; def->collOid = attribute->attcollation; def->constraints = NIL; @@ -2072,6 +2090,14 @@ MergeAttributes(List *schema, List *supers, char relpersistence, get_collation_name(defcollid), get_collation_name(newcollid)))); + /* + * def->identity is always ' ' because of the prohibition + * against inheriting from identity columns enforced earlier. + * The new column can have an identity definition, so we + * always just take that one. + */ + def->identity = newdef->identity; + /* Copy storage parameter */ if (def->storage == 0) def->storage = newdef->storage; @@ -3212,6 +3238,9 @@ AlterTableGetLockLevel(List *cmds) case AT_DisableRowSecurity: case AT_ForceRowSecurity: case AT_NoForceRowSecurity: + case AT_AddIdentity: + case AT_DropIdentity: + case AT_SetIdentity: cmd_lockmode = AccessExclusiveLock; break; @@ -3430,6 +3459,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_ADD_COL; break; case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ + case AT_AddIdentity: + case AT_DropIdentity: /* * We allow defaults on views so that INSERT into a view can have @@ -3442,6 +3473,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP; break; + case AT_SetIdentity: + ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + pass = AT_PASS_COL_ATTRS; + break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ATPrepDropNotNull(rel, recurse, recursing); @@ -3767,6 +3803,15 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode); break; + case AT_AddIdentity: + address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode); + break; + case AT_SetIdentity: + address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode); + break; + case AT_DropIdentity: + address = ATExecDropIdentity(rel, cmd->name, lockmode); + break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ address = ATExecDropNotNull(rel, cmd->name, lockmode); break; @@ -5119,6 +5164,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, elog(ERROR, "cache lookup failed for relation %u", myrelid); relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind; + if (colDef->identity != ' ' && find_inheritance_children(myrelid, NoLock) != NIL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot add identity column to table that has child tables"))); + /* skip if the name already exists and if_not_exists is true */ if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists)) { @@ -5171,6 +5221,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, attribute.attalign = tform->typalign; attribute.attnotnull = colDef->is_not_null; attribute.atthasdef = false; + attribute.attidentity = ' '; attribute.attisdropped = false; attribute.attislocal = colDef->is_local; attribute.attinhcount = colDef->inhcount; @@ -5480,6 +5531,7 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC cdef->is_local = true; cdef->is_not_null = true; cdef->storage = 0; + cdef->identity = ' '; cdef->location = -1; cmd->def = (Node *) cdef; } @@ -5759,6 +5811,12 @@ ATExecColumnDefault(Relation rel, const char *colName, errmsg("cannot alter system column \"%s\"", colName))); + if (get_attidentity(RelationGetRelid(rel), attnum) != ' ') + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column \"%s\" of relation \"%s\" is an identity column", + colName, RelationGetRelationName(rel)))); + /* * Remove any old default for the column. We use RESTRICT here for * safety, but at present we do not expect anything to depend on the @@ -5794,6 +5852,225 @@ ATExecColumnDefault(Relation rel, const char *colName, } /* + * ALTER TABLE ALTER COLUMN ADD IDENTITY + * + * Return the address of the affected column. + */ +static ObjectAddress +ATExecAddIdentity(Relation rel, const char *colName, + Node *def, LOCKMODE lockmode) +{ + Relation attrelation; + HeapTuple tuple; + Form_pg_attribute attTup; + AttrNumber attnum; + RawColumnDefault *rawEnt; + ObjectAddress address; + ColumnDef *cdef = (ColumnDef *) def; + + Assert(IsA(def, ColumnDef)); + + attrelation = heap_open(AttributeRelationId, RowExclusiveLock); + + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); + attTup = (Form_pg_attribute) GETSTRUCT(tuple); + attnum = attTup->attnum; + + /* Can't alter a system attribute */ + if (attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter system column \"%s\"", + colName))); + + /* + * MergeAttributes() disallows inheriting from table with identity column, + * so we have to prevent adding one later on. + */ + if (find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot add identity to table that has child tables"))); + + /* + * Creating a column as identity implies NOT NULL, so adding the identity + * to an existing column that is not NOT NULL would create a state that + * cannot be reproduced without contortions. + */ + if (!attTup->attnotnull) + ereport(ERROR, + (errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added", + colName, RelationGetRelationName(rel)))); + + if (attTup->attidentity != ' ') + ereport(ERROR, + (errmsg("column \"%s\" of relation \"%s\" is already an identity column", + colName, RelationGetRelationName(rel)))); + + if (attTup->atthasdef) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column \"%s\" of relation \"%s\" already has a default value", + colName, RelationGetRelationName(rel)))); + + attTup->attidentity = cdef->identity; + simple_heap_update(attrelation, &tuple->t_self, tuple); + CatalogUpdateIndexes(attrelation, tuple); + + InvokeObjectPostAlterHook(RelationRelationId, + RelationGetRelid(rel), + attTup->attnum); + ObjectAddressSubSet(address, RelationRelationId, + RelationGetRelid(rel), attnum); + heap_freetuple(tuple); + + CommandCounterIncrement(); + + rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); + rawEnt->attnum = attnum; + rawEnt->raw_default = cdef->raw_default; + + AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, + false, true, false); + + heap_close(attrelation, RowExclusiveLock); + + return address; +} + +static ObjectAddress +ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode) +{ + ListCell *option; + DefElem *generatedEl = NULL; + HeapTuple tuple; + Form_pg_attribute attTup; + AttrNumber attnum; + Relation attrelation; + ObjectAddress address; + + foreach(option, (List *) def) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "generated") == 0) + { + if (generatedEl) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + generatedEl = defel; + } + else + elog(ERROR, "option \"%s\" not recognized", + defel->defname); + } + + /* + * Even if there is nothing to change here, we run all the checks. There + * will be a subsequent ALTER SEQUENCE that relies on everything being + * there. + */ + + attrelation = heap_open(AttributeRelationId, RowExclusiveLock); + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); + + attTup = (Form_pg_attribute) GETSTRUCT(tuple); + attnum = attTup->attnum; + + if (attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter system column \"%s\"", + colName))); + + if (attTup->attidentity == ' ') + ereport(ERROR, + (errmsg("column \"%s\" of relation \"%s\" is not an identity column", + colName, RelationGetRelationName(rel)))); + + if (generatedEl) + { + attTup->attidentity = defGetInt32(generatedEl); + simple_heap_update(attrelation, &tuple->t_self, tuple); + CatalogUpdateIndexes(attrelation, tuple); + + InvokeObjectPostAlterHook(RelationRelationId, + RelationGetRelid(rel), + attTup->attnum); + ObjectAddressSubSet(address, RelationRelationId, + RelationGetRelid(rel), attnum); + } + + heap_freetuple(tuple); + heap_close(attrelation, RowExclusiveLock); + + return address; +} + +static ObjectAddress +ATExecDropIdentity(Relation rel, const char *colName, LOCKMODE lockmode) +{ + HeapTuple tuple; + Form_pg_attribute attTup; + AttrNumber attnum; + Relation attrelation; + ObjectAddress address; + + attrelation = heap_open(AttributeRelationId, RowExclusiveLock); + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colName, RelationGetRelationName(rel)))); + + attTup = (Form_pg_attribute) GETSTRUCT(tuple); + attnum = attTup->attnum; + + if (attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter system column \"%s\"", + colName))); + + if (attTup->attidentity == ' ') + ereport(ERROR, + (errmsg("column \"%s\" of relation \"%s\" is not an identity column", + colName, RelationGetRelationName(rel)))); + + attTup->attidentity = ' '; + simple_heap_update(attrelation, &tuple->t_self, tuple); + CatalogUpdateIndexes(attrelation, tuple); + + InvokeObjectPostAlterHook(RelationRelationId, + RelationGetRelid(rel), + attTup->attnum); + ObjectAddressSubSet(address, RelationRelationId, + RelationGetRelid(rel), attnum); + heap_freetuple(tuple); + + CommandCounterIncrement(); + + RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, + true); + + heap_close(attrelation, RowExclusiveLock); + + return address; +} + +/* * ALTER TABLE ALTER COLUMN SET STATISTICS */ static void @@ -8426,6 +8703,14 @@ ATPrepAlterColumnType(List **wqueue, errmsg("cannot alter inherited column \"%s\"", colName))); + /* Don't alter identity columns. (XXX This could be done, but needs more + * work to check permitted types, adjust sequence.) */ + if (attTup->attidentity != ' ') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot alter data type of identity column \"%s\"", + colName))); + /* Don't alter columns used in the partition key */ if (is_partition_attr(rel, attnum, &is_expr)) { diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 6955298577..a9eb4029e1 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2623,6 +2623,7 @@ _copyColumnDef(const ColumnDef *from) COPY_SCALAR_FIELD(storage); COPY_NODE_FIELD(raw_default); COPY_NODE_FIELD(cooked_default); + COPY_SCALAR_FIELD(identity); COPY_NODE_FIELD(collClause); COPY_SCALAR_FIELD(collOid); COPY_NODE_FIELD(constraints); @@ -2645,6 +2646,7 @@ _copyConstraint(const Constraint *from) COPY_SCALAR_FIELD(is_no_inherit); COPY_NODE_FIELD(raw_expr); COPY_STRING_FIELD(cooked_expr); + COPY_SCALAR_FIELD(generated_when); COPY_NODE_FIELD(keys); COPY_NODE_FIELD(exclusions); COPY_NODE_FIELD(options); @@ -2753,6 +2755,7 @@ _copyQuery(const Query *from) COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(jointree); COPY_NODE_FIELD(targetList); + COPY_SCALAR_FIELD(override); COPY_NODE_FIELD(onConflict); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(groupClause); @@ -2782,6 +2785,7 @@ _copyInsertStmt(const InsertStmt *from) COPY_NODE_FIELD(onConflictClause); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(withClause); + COPY_SCALAR_FIELD(override); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 548a2aa876..6b3ae4d81d 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -932,6 +932,7 @@ _equalQuery(const Query *a, const Query *b) COMPARE_NODE_FIELD(rtable); COMPARE_NODE_FIELD(jointree); COMPARE_NODE_FIELD(targetList); + COMPARE_SCALAR_FIELD(override); COMPARE_NODE_FIELD(onConflict); COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(groupClause); @@ -959,6 +960,7 @@ _equalInsertStmt(const InsertStmt *a, const InsertStmt *b) COMPARE_NODE_FIELD(onConflictClause); COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(withClause); + COMPARE_SCALAR_FIELD(override); return true; } @@ -2382,6 +2384,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b) COMPARE_SCALAR_FIELD(storage); COMPARE_NODE_FIELD(raw_default); COMPARE_NODE_FIELD(cooked_default); + COMPARE_SCALAR_FIELD(identity); COMPARE_NODE_FIELD(collClause); COMPARE_SCALAR_FIELD(collOid); COMPARE_NODE_FIELD(constraints); @@ -2402,6 +2405,7 @@ _equalConstraint(const Constraint *a, const Constraint *b) COMPARE_SCALAR_FIELD(is_no_inherit); COMPARE_NODE_FIELD(raw_expr); COMPARE_STRING_FIELD(cooked_expr); + COMPARE_SCALAR_FIELD(generated_when); COMPARE_NODE_FIELD(keys); COMPARE_NODE_FIELD(exclusions); COMPARE_NODE_FIELD(options); diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index c97532b348..8166bd35ea 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -497,6 +497,7 @@ makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid) n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; + n->identity = ' '; n->collClause = NULL; n->collOid = collOid; n->constraints = NIL; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 9fe98739c1..df723b41f3 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2587,6 +2587,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node) WRITE_CHAR_FIELD(storage); WRITE_NODE_FIELD(raw_default); WRITE_NODE_FIELD(cooked_default); + WRITE_CHAR_FIELD(identity); WRITE_NODE_FIELD(collClause); WRITE_OID_FIELD(collOid); WRITE_NODE_FIELD(constraints); @@ -2692,6 +2693,7 @@ _outQuery(StringInfo str, const Query *node) WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(jointree); WRITE_NODE_FIELD(targetList); + WRITE_ENUM_FIELD(override, OverridingKind); WRITE_NODE_FIELD(onConflict); WRITE_NODE_FIELD(returningList); WRITE_NODE_FIELD(groupClause); @@ -3188,6 +3190,13 @@ _outConstraint(StringInfo str, const Constraint *node) WRITE_STRING_FIELD(cooked_expr); break; + case CONSTR_IDENTITY: + appendStringInfoString(str, "IDENTITY"); + WRITE_NODE_FIELD(raw_expr); + WRITE_STRING_FIELD(cooked_expr); + WRITE_CHAR_FIELD(generated_when); + break; + case CONSTR_CHECK: appendStringInfoString(str, "CHECK"); WRITE_BOOL_FIELD(is_no_inherit); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 63f633634c..607168acfc 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -249,6 +249,7 @@ _readQuery(void) READ_NODE_FIELD(rtable); READ_NODE_FIELD(jointree); READ_NODE_FIELD(targetList); + READ_ENUM_FIELD(override, OverridingKind); READ_NODE_FIELD(onConflict); READ_NODE_FIELD(returningList); READ_NODE_FIELD(groupClause); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index a558083f43..70e49fde7b 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -464,6 +464,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } + qry->override = stmt->override; + isOnConflictUpdate = (stmt->onConflictClause && stmt->onConflictClause->action == ONCONFLICT_UPDATE); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 08cf5b78f5..74856fc043 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -281,6 +281,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <node> alter_table_cmd alter_type_cmd opt_collate_clause replica_identity partition_cmd %type <list> alter_table_cmds alter_type_cmds +%type <list> alter_identity_column_option_list +%type <defelt> alter_identity_column_option %type <dbehavior> opt_drop_behavior @@ -432,7 +434,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); select_offset_value2 opt_select_fetch_first_value %type <ival> row_or_rows first_or_next -%type <list> OptSeqOptList SeqOptList +%type <list> OptSeqOptList SeqOptList OptParenthesizedSeqOptList %type <defelt> SeqOptElem %type <istmt> insert_rest @@ -548,6 +550,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); opt_frame_clause frame_extent frame_bound %type <str> opt_existing_window_name %type <boolean> opt_if_not_exists +%type <ival> generated_when override_kind %type <partspec> PartitionSpec OptPartitionSpec %type <str> part_strategy %type <partelem> part_elem @@ -610,7 +613,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS - GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING + GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING HANDLER HAVING HEADER_P HOLD HOUR_P @@ -634,7 +637,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); NULLS_P NUMERIC OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR - ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER + ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY @@ -705,6 +708,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * same as if they weren't keywords). We need to do this for PARTITION, * RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS * so that they can follow a_expr without creating postfix-operator problems; + * for GENERATED so that it can follow b_expr; * and for NULL so that it can follow b_expr in ColQualList without creating * postfix-operator problems. * @@ -723,7 +727,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * blame any funny behavior of UNBOUNDED on the SQL standard, though. */ %nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */ -%nonassoc IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP +%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' @@ -2091,6 +2095,40 @@ alter_table_cmd: n->def = (Node *) makeString($6); $$ = (Node *)n; } + /* ALTER TABLE <name> ALTER [COLUMN] <colname> ADD GENERATED ... AS IDENTITY ... */ + | ALTER opt_column ColId ADD_P GENERATED generated_when AS IDENTITY_P OptParenthesizedSeqOptList + { + AlterTableCmd *n = makeNode(AlterTableCmd); + Constraint *c = makeNode(Constraint); + + c->contype = CONSTR_IDENTITY; + c->generated_when = $6; + c->options = $9; + c->location = @5; + + n->subtype = AT_AddIdentity; + n->name = $3; + n->def = (Node *) c; + + $$ = (Node *)n; + } + /* ALTER TABLE <name> ALTER [COLUMN] <colname> SET <sequence options>/RESET */ + | ALTER opt_column ColId alter_identity_column_option_list + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetIdentity; + n->name = $3; + n->def = (Node *) $4; + $$ = (Node *)n; + } + /* ALTER TABLE <name> ALTER [COLUMN] <colname> DROP IDENTITY */ + | ALTER opt_column ColId DROP IDENTITY_P + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_DropIdentity; + n->name = $3; + $$ = (Node *)n; + } /* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */ | DROP opt_column IF_P EXISTS ColId opt_drop_behavior { @@ -2528,6 +2566,38 @@ reloption_elem: } ; +alter_identity_column_option_list: + alter_identity_column_option + { $$ = list_make1($1); } + | alter_identity_column_option_list alter_identity_column_option + { $$ = lappend($1, $2); } + ; + +alter_identity_column_option: + RESTART + { + $$ = makeDefElem("restart", NULL, @1); + } + | RESTART opt_with NumericOnly + { + $$ = makeDefElem("restart", (Node *)$3, @1); + } + | SET SeqOptElem + { + if (strcmp($2->defname, "restart") == 0 || + strcmp($2->defname, "owned_by") == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("sequence option \"%s\" not supported here", $2->defname), + parser_errposition(@2))); + $$ = $2; + } + | SET GENERATED generated_when + { + $$ = makeDefElem("generated", (Node *) makeInteger($3), @1); + } + ; + ForValues: /* a LIST partition */ FOR VALUES IN_P '(' partbound_datum_list ')' @@ -3178,6 +3248,7 @@ columnDef: ColId Typename create_generic_options ColQualList n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; + n->identity = ' '; n->collOid = InvalidOid; n->fdwoptions = $3; SplitColQualList($4, &n->constraints, &n->collClause, @@ -3199,6 +3270,7 @@ columnOptions: ColId WITH OPTIONS ColQualList n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; + n->identity = ' '; n->collOid = InvalidOid; SplitColQualList($4, &n->constraints, &n->collClause, yyscanner); @@ -3311,6 +3383,15 @@ ColConstraintElem: n->cooked_expr = NULL; $$ = (Node *)n; } + | GENERATED generated_when AS IDENTITY_P OptParenthesizedSeqOptList + { + Constraint *n = makeNode(Constraint); + n->contype = CONSTR_IDENTITY; + n->generated_when = $2; + n->options = $5; + n->location = @1; + $$ = (Node *)n; + } | REFERENCES qualified_name opt_column_list key_match key_actions { Constraint *n = makeNode(Constraint); @@ -3328,6 +3409,11 @@ ColConstraintElem: } ; +generated_when: + ALWAYS { $$ = CONSTR_GENERATED_ALWAYS; } + | BY DEFAULT { $$ = CONSTR_GENERATED_DEFAULT; } + ; + /* * ConstraintAttr represents constraint attributes, which we parse as if * they were independent constraint clauses, in order to avoid shift/reduce @@ -3394,6 +3480,7 @@ TableLikeOptionList: TableLikeOption: DEFAULTS { $$ = CREATE_TABLE_LIKE_DEFAULTS; } | CONSTRAINTS { $$ = CREATE_TABLE_LIKE_CONSTRAINTS; } + | IDENTITY_P { $$ = CREATE_TABLE_LIKE_IDENTITY; } | INDEXES { $$ = CREATE_TABLE_LIKE_INDEXES; } | STORAGE { $$ = CREATE_TABLE_LIKE_STORAGE; } | COMMENTS { $$ = CREATE_TABLE_LIKE_COMMENTS; } @@ -3903,6 +3990,10 @@ OptSeqOptList: SeqOptList { $$ = $1; } | /*EMPTY*/ { $$ = NIL; } ; +OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + SeqOptList: SeqOptElem { $$ = list_make1($1); } | SeqOptList SeqOptElem { $$ = lappend($1, $2); } ; @@ -3943,6 +4034,11 @@ SeqOptElem: CACHE NumericOnly { $$ = makeDefElem("owned_by", (Node *)$3, @1); } + | SEQUENCE NAME_P any_name + { + /* not documented, only used by pg_dump */ + $$ = makeDefElem("sequence_name", (Node *)$3, @1); + } | START opt_with NumericOnly { $$ = makeDefElem("start", (Node *)$3, @1); @@ -10070,12 +10166,26 @@ insert_rest: $$->cols = NIL; $$->selectStmt = $1; } + | OVERRIDING override_kind VALUE_P SelectStmt + { + $$ = makeNode(InsertStmt); + $$->cols = NIL; + $$->override = $2; + $$->selectStmt = $4; + } | '(' insert_column_list ')' SelectStmt { $$ = makeNode(InsertStmt); $$->cols = $2; $$->selectStmt = $4; } + | '(' insert_column_list ')' OVERRIDING override_kind VALUE_P SelectStmt + { + $$ = makeNode(InsertStmt); + $$->cols = $2; + $$->override = $5; + $$->selectStmt = $7; + } | DEFAULT VALUES { $$ = makeNode(InsertStmt); @@ -10084,6 +10194,11 @@ insert_rest: } ; +override_kind: + USER { $$ = OVERRIDING_USER_VALUE; } + | SYSTEM_P { $$ = OVERRIDING_SYSTEM_VALUE; } + ; + insert_column_list: insert_column_item { $$ = list_make1($1); } @@ -11434,6 +11549,7 @@ TableFuncElement: ColId Typename opt_collate_clause n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; + n->identity = ' '; n->collClause = (CollateClause *) $3; n->collOid = InvalidOid; n->constraints = NIL; @@ -14091,6 +14207,7 @@ unreserved_keyword: | FORWARD | FUNCTION | FUNCTIONS + | GENERATED | GLOBAL | GRANTED | HANDLER @@ -14159,6 +14276,7 @@ unreserved_keyword: | OPTIONS | ORDINALITY | OVER + | OVERRIDING | OWNED | OWNER | PARALLEL diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 4f74208633..f301fa614e 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -42,6 +42,7 @@ #include "catalog/pg_type.h" #include "commands/comment.h" #include "commands/defrem.h" +#include "commands/sequence.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" #include "miscadmin.h" @@ -356,6 +357,150 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) return result; } +static void +generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, List *seqoptions, DependencyType deptype, + char **snamespace_p, char **sname_p) +{ + ListCell *option; + DefElem *nameEl = NULL; + Oid snamespaceid; + char *snamespace; + char *sname; + CreateSeqStmt *seqstmt; + AlterSeqStmt *altseqstmt; + List *attnamelist; + + /* + * Determine namespace and name to use for the sequence. + * + * First, check if a sequence name was passed in as an option. This is + * used by pg_dump. Else, generate a name. + * + * Although we use ChooseRelationName, it's not guaranteed that the + * selected sequence name won't conflict; given sufficiently long + * field names, two different serial columns in the same table could + * be assigned the same sequence name, and we'd not notice since we + * aren't creating the sequence quite yet. In practice this seems + * quite unlikely to be a problem, especially since few people would + * need two serial columns in one table. + */ + + foreach(option, seqoptions) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "sequence_name") == 0) + { + if (nameEl) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + nameEl = defel; + } + } + + if (nameEl) + { + RangeVar *rv = makeRangeVarFromNameList((List *) nameEl->arg); + snamespace = rv->schemaname; + sname = rv->relname; + list_delete_ptr(seqoptions, nameEl); + } + else + { + if (cxt->rel) + snamespaceid = RelationGetNamespace(cxt->rel); + else + { + snamespaceid = RangeVarGetCreationNamespace(cxt->relation); + RangeVarAdjustRelationPersistence(cxt->relation, snamespaceid); + } + snamespace = get_namespace_name(snamespaceid); + sname = ChooseRelationName(cxt->relation->relname, + column->colname, + "seq", + snamespaceid); + } + + ereport(DEBUG1, + (errmsg("%s will create implicit sequence \"%s\" for serial column \"%s.%s\"", + cxt->stmtType, sname, + cxt->relation->relname, column->colname))); + + /* + * Build a CREATE SEQUENCE command to create the sequence object, and + * add it to the list of things to be done before this CREATE/ALTER + * TABLE. + */ + seqstmt = makeNode(CreateSeqStmt); + seqstmt->sequence = makeRangeVar(snamespace, sname, -1); + seqstmt->options = seqoptions; + + /* + * If this is ALTER ADD COLUMN, make sure the sequence will be owned + * by the table's owner. The current user might be someone else + * (perhaps a superuser, or someone who's only a member of the owning + * role), but the SEQUENCE OWNED BY mechanisms will bleat unless table + * and sequence have exactly the same owning role. + */ + if (cxt->rel) + seqstmt->ownerId = cxt->rel->rd_rel->relowner; + else + seqstmt->ownerId = InvalidOid; + + cxt->blist = lappend(cxt->blist, seqstmt); + + /* + * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence + * as owned by this column, and add it to the list of things to be + * done after this CREATE/ALTER TABLE. + */ + altseqstmt = makeNode(AlterSeqStmt); + altseqstmt->sequence = makeRangeVar(snamespace, sname, -1); + attnamelist = list_make3(makeString(snamespace), + makeString(cxt->relation->relname), + makeString(column->colname)); + altseqstmt->options = list_make2(makeDefElem("owned_by", + (Node *) attnamelist, -1), + makeDefElem("deptype", + (Node *) makeInteger(deptype), -1)); + + cxt->alist = lappend(cxt->alist, altseqstmt); + + *snamespace_p = snamespace; + *sname_p = sname; +} + +/* + * Create an expression tree representing the function call + * nextval('sequencename'). We cannot reduce the raw tree to cooked + * form until after the sequence is created, but there's no need to do + * so. + */ +static FuncCall * +generateNextvalExpr(char *snamespace, char *sname) +{ + char *qstring; + A_Const *snamenode; + TypeCast *castnode; + FuncCall *funccallnode; + + qstring = quote_qualified_identifier(snamespace, sname); + snamenode = makeNode(A_Const); + snamenode->val.type = T_String; + snamenode->val.val.str = qstring; + snamenode->location = -1; + castnode = makeNode(TypeCast); + castnode->typeName = SystemTypeName("regclass"); + castnode->arg = (Node *) snamenode; + castnode->location = -1; + funccallnode = makeFuncCall(SystemFuncName("nextval"), + list_make1(castnode), + -1); + + return funccallnode; +} + /* * transformColumnDefinition - * transform a single ColumnDef within CREATE TABLE @@ -367,7 +512,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) bool is_serial; bool saw_nullable; bool saw_default; - Constraint *constraint; + bool saw_identity; ListCell *clist; cxt->columns = lappend(cxt->columns, column); @@ -422,110 +567,21 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) /* Special actions for SERIAL pseudo-types */ if (is_serial) { - Oid snamespaceid; char *snamespace; char *sname; - char *qstring; - A_Const *snamenode; - TypeCast *castnode; - FuncCall *funccallnode; - CreateSeqStmt *seqstmt; - AlterSeqStmt *altseqstmt; - List *attnamelist; - - /* - * Determine namespace and name to use for the sequence. - * - * Although we use ChooseRelationName, it's not guaranteed that the - * selected sequence name won't conflict; given sufficiently long - * field names, two different serial columns in the same table could - * be assigned the same sequence name, and we'd not notice since we - * aren't creating the sequence quite yet. In practice this seems - * quite unlikely to be a problem, especially since few people would - * need two serial columns in one table. - */ - if (cxt->rel) - snamespaceid = RelationGetNamespace(cxt->rel); - else - { - snamespaceid = RangeVarGetCreationNamespace(cxt->relation); - RangeVarAdjustRelationPersistence(cxt->relation, snamespaceid); - } - snamespace = get_namespace_name(snamespaceid); - sname = ChooseRelationName(cxt->relation->relname, - column->colname, - "seq", - snamespaceid); - - ereport(DEBUG1, - (errmsg("%s will create implicit sequence \"%s\" for serial column \"%s.%s\"", - cxt->stmtType, sname, - cxt->relation->relname, column->colname))); - - /* - * Build a CREATE SEQUENCE command to create the sequence object, and - * add it to the list of things to be done before this CREATE/ALTER - * TABLE. - */ - seqstmt = makeNode(CreateSeqStmt); - seqstmt->sequence = makeRangeVar(snamespace, sname, -1); - seqstmt->options = NIL; - - /* - * If this is ALTER ADD COLUMN, make sure the sequence will be owned - * by the table's owner. The current user might be someone else - * (perhaps a superuser, or someone who's only a member of the owning - * role), but the SEQUENCE OWNED BY mechanisms will bleat unless table - * and sequence have exactly the same owning role. - */ - if (cxt->rel) - seqstmt->ownerId = cxt->rel->rd_rel->relowner; - else - seqstmt->ownerId = InvalidOid; - - cxt->blist = lappend(cxt->blist, seqstmt); - - /* - * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence - * as owned by this column, and add it to the list of things to be - * done after this CREATE/ALTER TABLE. - */ - altseqstmt = makeNode(AlterSeqStmt); - altseqstmt->sequence = makeRangeVar(snamespace, sname, -1); - attnamelist = list_make3(makeString(snamespace), - makeString(cxt->relation->relname), - makeString(column->colname)); - altseqstmt->options = list_make1(makeDefElem("owned_by", - (Node *) attnamelist, -1)); + Constraint *constraint; - cxt->alist = lappend(cxt->alist, altseqstmt); + generateSerialExtraStmts(cxt, column, NIL, DEPENDENCY_AUTO, &snamespace, &sname); /* * Create appropriate constraints for SERIAL. We do this in full, * rather than shortcutting, so that we will detect any conflicting * constraints the user wrote (like a different DEFAULT). - * - * Create an expression tree representing the function call - * nextval('sequencename'). We cannot reduce the raw tree to cooked - * form until after the sequence is created, but there's no need to do - * so. */ - qstring = quote_qualified_identifier(snamespace, sname); - snamenode = makeNode(A_Const); - snamenode->val.type = T_String; - snamenode->val.val.str = qstring; - snamenode->location = -1; - castnode = makeNode(TypeCast); - castnode->typeName = SystemTypeName("regclass"); - castnode->arg = (Node *) snamenode; - castnode->location = -1; - funccallnode = makeFuncCall(SystemFuncName("nextval"), - list_make1(castnode), - -1); constraint = makeNode(Constraint); constraint->contype = CONSTR_DEFAULT; constraint->location = -1; - constraint->raw_expr = (Node *) funccallnode; + constraint->raw_expr = (Node *) generateNextvalExpr(snamespace, sname); constraint->cooked_expr = NULL; column->constraints = lappend(column->constraints, constraint); @@ -540,9 +596,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) saw_nullable = false; saw_default = false; + saw_identity = false; foreach(clist, column->constraints) { + Constraint *constraint; + constraint = lfirst(clist); Assert(IsA(constraint, Constraint)); @@ -582,9 +641,43 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) constraint->location))); column->raw_default = constraint->raw_expr; Assert(constraint->cooked_expr == NULL); + column->identity = ' '; saw_default = true; break; + case CONSTR_IDENTITY: + { + char *snamespace; + char *sname; + Type ctype; + Oid typeOid; + + ctype = typenameType(cxt->pstate, column->typeName, NULL); + typeOid = HeapTupleGetOid(ctype); + ReleaseSysCache(ctype); + + if (!(typeOid == INT2OID || typeOid == INT4OID || typeOid == INT8OID)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("identity column type must be smallint, integer, or bigint"))); + + if (saw_identity) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple identity specifications for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + + generateSerialExtraStmts(cxt, column, constraint->options, DEPENDENCY_INTERNAL, &snamespace, &sname); + column->raw_default = (Node *) generateNextvalExpr(snamespace, sname); + Assert(constraint->cooked_expr == NULL); + column->identity = constraint->generated_when; + saw_identity = true; + column->is_not_null = TRUE; + break; + } + case CONSTR_CHECK: cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); break; @@ -661,6 +754,14 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) constraint->contype); break; } + + if (saw_default && saw_identity) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("both default and identity specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); } /* @@ -890,6 +991,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla def->storage = 0; def->raw_default = NULL; def->cooked_default = NULL; + def->identity = ' '; def->collClause = NULL; def->collOid = attribute->attcollation; def->constraints = NIL; @@ -905,7 +1007,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla /* * Copy default, if present and the default has been requested */ - if (attribute->atthasdef && + if (attribute->atthasdef && attribute->attidentity == ' ' && (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)) { Node *this_default = NULL; @@ -933,6 +1035,30 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla def->cooked_default = this_default; } + /* + * Copy identity if requested + */ + if (attribute->attidentity != ' ' && + (table_like_clause->options & CREATE_TABLE_LIKE_IDENTITY)) + { + List *seqlist; + Oid seq_relid; + List *seq_options; + char *snamespace; + char *sname; + + /* + * find sequence owned by old column; extract sequence parameters; + * build new default and create sequence commands + */ + seqlist = getOwnedSequences(RelationGetRelid(relation), attribute->attnum); + seq_relid = linitial_oid(seqlist); + seq_options = sequence_options(seq_relid); + generateSerialExtraStmts(cxt, def, seq_options, DEPENDENCY_INTERNAL, &snamespace, &sname); + def->raw_default = (Node *) generateNextvalExpr(snamespace, sname); + def->identity = attribute->attidentity; + } + /* Likewise, copy storage if requested */ if (table_like_clause->options & CREATE_TABLE_LIKE_STORAGE) def->storage = attribute->attstorage; @@ -1109,6 +1235,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename) n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; + n->identity = ' '; n->collClause = NULL; n->collOid = attr->attcollation; n->constraints = NIL; @@ -2653,6 +2780,87 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, break; } + case AT_AddIdentity: + { + Constraint *def = (Constraint *) cmd->def; + ColumnDef *newdef = makeNode(ColumnDef); + Oid typeOid; + char *snamespace; + char *sname; + + Assert(IsA(def, Constraint)); + + typeOid = get_atttype(relid, get_attnum(relid, cmd->name)); + if (!(typeOid == INT2OID || typeOid == INT4OID || typeOid == INT8OID)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("identity column type must be smallint, integer, or bigint"))); + + newdef->colname = cmd->name; // XXX before below FIXME + generateSerialExtraStmts(&cxt, newdef, def->options, DEPENDENCY_INTERNAL, &snamespace, &sname); + newdef->raw_default = (Node *) generateNextvalExpr(snamespace, sname); + newdef->identity = def->generated_when; + + cmd->def = (Node *) newdef; + + newcmds = lappend(newcmds, cmd); + break; + } + + case AT_SetIdentity: + { + /* + * Create an ALTER SEQUENCE statement for the internal + * sequence of the identity column. + */ + ListCell *lc; + List *newseqopts = NIL; + List *newdef = NIL; + List *seqlist; + AttrNumber attnum; + + /* + * Split options into those handled by ALTER SEQUENCE and + * those for ALTER TABLE proper. + */ + foreach(lc, (List *) cmd->def) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "generated") == 0) + newdef = lappend(newdef, def); + else + newseqopts = lappend(newseqopts, def); + } + + attnum = get_attnum(relid, cmd->name); + + if (attnum) + { + seqlist = getOwnedSequences(relid, attnum); + if (seqlist) + { + AlterSeqStmt *seqstmt; + Oid seq_relid; + + seqstmt = makeNode(AlterSeqStmt); + seq_relid = linitial_oid(seqlist); + seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)), + get_rel_name(seq_relid), -1); + seqstmt->options = newseqopts; + seqstmt->missing_ok = false; + + cxt.alist = lappend(cxt.alist, seqstmt); + } + } + /* If column was not found or did not own sequences, we + * just let the ALTER TABLE command error out. */ + + cmd->def = (Node *) newdef; + newcmds = lappend(newcmds, cmd); + break; + } + case AT_AttachPartition: { PartitionCmd *partcmd = (PartitionCmd *) cmd->def; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index bf4f098c15..5c9f3005e0 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -61,6 +61,7 @@ static Query *rewriteRuleAction(Query *parsetree, static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index); static List *rewriteTargetListIU(List *targetList, CmdType commandType, + OverridingKind override, Relation target_relation, int result_rti, List **attrno_list); @@ -705,6 +706,7 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) static List * rewriteTargetListIU(List *targetList, CmdType commandType, + OverridingKind override, Relation target_relation, int result_rti, List **attrno_list) @@ -785,6 +787,7 @@ rewriteTargetListIU(List *targetList, for (attrno = 1; attrno <= numattrs; attrno++) { TargetEntry *new_tle = new_tles[attrno - 1]; + bool apply_default; att_tup = target_relation->rd_att->attrs[attrno - 1]; @@ -797,8 +800,37 @@ rewriteTargetListIU(List *targetList, * it's an INSERT and there's no tlist entry for the column, or the * tlist entry is a DEFAULT placeholder node. */ - if ((new_tle == NULL && commandType == CMD_INSERT) || - (new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault))) + apply_default =((new_tle == NULL && commandType == CMD_INSERT) || + (new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault))); + + if (commandType == CMD_INSERT) + { + if (att_tup->attidentity == CONSTR_GENERATED_ALWAYS && !apply_default) + { + if (override != OVERRIDING_SYSTEM_VALUE) + ereport(ERROR, + (errcode(ERRCODE_GENERATED_ALWAYS), + errmsg("cannot insert into column \"%s\"", NameStr(att_tup->attname)), + errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.", + NameStr(att_tup->attname)), + errhint("Use OVERRIDING SYSTEM VALUE to override."))); + } + + if (att_tup->attidentity == CONSTR_GENERATED_DEFAULT && override == OVERRIDING_USER_VALUE) + apply_default = true; + } + + if (commandType == CMD_UPDATE) + { + if (att_tup->attidentity == CONSTR_GENERATED_ALWAYS && !apply_default) + ereport(ERROR, + (errcode(ERRCODE_GENERATED_ALWAYS), + errmsg("column \"%s\" can only be updated to DEFAULT", NameStr(att_tup->attname)), + errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.", + NameStr(att_tup->attname)))); + } + + if (apply_default) { Node *new_expr; @@ -3233,6 +3265,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events) /* Process the main targetlist ... */ parsetree->targetList = rewriteTargetListIU(parsetree->targetList, parsetree->commandType, + parsetree->override, rt_entry_relation, parsetree->resultRelation, &attrnos); @@ -3245,6 +3278,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events) parsetree->targetList = rewriteTargetListIU(parsetree->targetList, parsetree->commandType, + parsetree->override, rt_entry_relation, parsetree->resultRelation, NULL); } @@ -3255,6 +3289,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events) parsetree->onConflict->onConflictSet = rewriteTargetListIU(parsetree->onConflict->onConflictSet, CMD_UPDATE, + parsetree->override, rt_entry_relation, parsetree->resultRelation, NULL); @@ -3264,7 +3299,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events) { parsetree->targetList = rewriteTargetListIU(parsetree->targetList, - parsetree->commandType, rt_entry_relation, + parsetree->commandType, + parsetree->override, + rt_entry_relation, parsetree->resultRelation, NULL); rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation); } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 4e2ba19d1b..13db529927 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -5790,6 +5790,14 @@ get_insert_query_def(Query *query, deparse_context *context) if (query->targetList) appendStringInfoString(buf, ") "); + if (query->override) + { + if (query->override == OVERRIDING_SYSTEM_VALUE) + appendStringInfoString(buf, "OVERRIDING SYSTEM VALUE "); + else if (query->override == OVERRIDING_USER_VALUE) + appendStringInfoString(buf, "OVERRIDING USER VALUE "); + } + if (select_rte) { /* Add the SELECT */ diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 13ae6add03..2d7cdaad8d 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -836,6 +836,27 @@ get_attnum(Oid relid, const char *attname) return InvalidAttrNumber; } +char +get_attidentity(Oid relid, AttrNumber attnum) +{ + HeapTuple tp; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (HeapTupleIsValid(tp)) + { + Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp); + char result; + + result = att_tup->attidentity; + ReleaseSysCache(tp); + return result; + } + else + return 0; +} + /* * get_atttype * diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2a6835991c..7a0c30442d 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -3259,6 +3259,7 @@ RelationBuildLocalRelation(const char *relname, has_not_null = false; for (i = 0; i < natts; i++) { + rel->rd_att->attrs[i]->attidentity = tupDesc->attrs[i]->attidentity; rel->rd_att->attrs[i]->attnotnull = tupDesc->attrs[i]->attnotnull; has_not_null |= tupDesc->attrs[i]->attnotnull; } diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt index e7bdb925ac..2c5915beb0 100644 --- a/src/backend/utils/errcodes.txt +++ b/src/backend/utils/errcodes.txt @@ -326,6 +326,7 @@ Section: Class 42 - Syntax Error or Access Rule Violation 42P21 E ERRCODE_COLLATION_MISMATCH collation_mismatch 42P22 E ERRCODE_INDETERMINATE_COLLATION indeterminate_collation 42809 E ERRCODE_WRONG_OBJECT_TYPE wrong_object_type +428C9 E ERRCODE_GENERATED_ALWAYS generated_always # Note: for ERRCODE purposes, we divide namable objects into these categories: # databases, schemas, prepared statements, cursors, tables, columns, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e5545b31d4..6f02a214ca 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1885,6 +1885,9 @@ dumpTableData_insert(Archive *fout, void *dcontext) appendPQExpBufferStr(insertStmt, ") "); } + if (tbinfo->needs_override) + appendPQExpBufferStr(insertStmt, "OVERRIDING SYSTEM VALUE "); + appendPQExpBufferStr(insertStmt, "VALUES ("); } } @@ -4945,6 +4948,7 @@ getTables(Archive *fout, int *numTables) int i_toastreloptions; int i_reloftype; int i_relpages; + int i_is_identity_sequence; int i_changed_acl; /* Make sure we are in proper schema */ @@ -5021,6 +5025,7 @@ getTables(Archive *fout, int *numTables) "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, " "tc.reloptions AS toast_reloptions, " + "EXISTS (SELECT 1 FROM pg_depend WHERE classid = 'pg_class'::regclass AND objid = c.oid AND objsubid = 0 AND refclassid = 'pg_attrdef'::regclass AND deptype = 'i') AS is_identity_sequence, " "EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON " "(c.oid = pip.objoid " "AND pip.classoid = 'pg_class'::regclass " @@ -5472,6 +5477,7 @@ getTables(Archive *fout, int *numTables) i_checkoption = PQfnumber(res, "checkoption"); i_toastreloptions = PQfnumber(res, "toast_reloptions"); i_reloftype = PQfnumber(res, "reloftype"); + i_is_identity_sequence = PQfnumber(res, "is_identity_sequence"); i_changed_acl = PQfnumber(res, "changed_acl"); if (dopt->lockWaitTimeout) @@ -5572,6 +5578,8 @@ getTables(Archive *fout, int *numTables) tblinfo[i].dummy_view = false; /* might get set during sort */ tblinfo[i].postponed_def = false; /* might get set during sort */ + tblinfo[i].is_identity_sequence = (strcmp(PQgetvalue(res, i, i_is_identity_sequence), "t") == 0); + /* * Read-lock target tables to make sure they aren't DROPPED or altered * in schema before we get around to dumping them. @@ -7125,6 +7133,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) int i_typstorage; int i_attnotnull; int i_atthasdef; + int i_attidentity; int i_attisdropped; int i_attlen; int i_attalign; @@ -7167,7 +7176,34 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) resetPQExpBuffer(q); - if (fout->remoteVersion >= 90200) + if (fout->remoteVersion >= 100000) + { + /* + * attidentity is new in version 10. + */ + appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, " + "a.attstattarget, a.attstorage, t.typstorage, " + "a.attnotnull, a.atthasdef, a.attisdropped, " + "a.attlen, a.attalign, a.attislocal, " + "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, " + "array_to_string(a.attoptions, ', ') AS attoptions, " + "CASE WHEN a.attcollation <> t.typcollation " + "THEN a.attcollation ELSE 0 END AS attcollation, " + "a.attidentity, " + "pg_catalog.array_to_string(ARRAY(" + "SELECT pg_catalog.quote_ident(option_name) || " + "' ' || pg_catalog.quote_literal(option_value) " + "FROM pg_catalog.pg_options_to_table(attfdwoptions) " + "ORDER BY option_name" + "), E',\n ') AS attfdwoptions " + "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " + "ON a.atttypid = t.oid " + "WHERE a.attrelid = '%u'::pg_catalog.oid " + "AND a.attnum > 0::pg_catalog.int2 " + "ORDER BY a.attrelid, a.attnum", + tbinfo->dobj.catId.oid); + } + else if (fout->remoteVersion >= 90200) { /* * attfdwoptions is new in 9.2. @@ -7266,6 +7302,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) i_typstorage = PQfnumber(res, "typstorage"); i_attnotnull = PQfnumber(res, "attnotnull"); i_atthasdef = PQfnumber(res, "atthasdef"); + i_attidentity = PQfnumber(res, "attidentity"); i_attisdropped = PQfnumber(res, "attisdropped"); i_attlen = PQfnumber(res, "attlen"); i_attalign = PQfnumber(res, "attalign"); @@ -7281,6 +7318,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) tbinfo->attstattarget = (int *) pg_malloc(ntups * sizeof(int)); tbinfo->attstorage = (char *) pg_malloc(ntups * sizeof(char)); tbinfo->typstorage = (char *) pg_malloc(ntups * sizeof(char)); + tbinfo->attidentity = (char *) pg_malloc(ntups * sizeof(bool)); tbinfo->attisdropped = (bool *) pg_malloc(ntups * sizeof(bool)); tbinfo->attlen = (int *) pg_malloc(ntups * sizeof(int)); tbinfo->attalign = (char *) pg_malloc(ntups * sizeof(char)); @@ -7305,6 +7343,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) tbinfo->attstattarget[j] = atoi(PQgetvalue(res, j, i_attstattarget)); tbinfo->attstorage[j] = *(PQgetvalue(res, j, i_attstorage)); tbinfo->typstorage[j] = *(PQgetvalue(res, j, i_typstorage)); + tbinfo->attidentity[j] = *(PQgetvalue(res, j, i_attidentity)); + tbinfo->needs_override = tbinfo->needs_override || (tbinfo->attidentity[j] == 'a'); tbinfo->attisdropped[j] = (PQgetvalue(res, j, i_attisdropped)[0] == 't'); tbinfo->attlen[j] = atoi(PQgetvalue(res, j, i_attlen)); tbinfo->attalign[j] = *(PQgetvalue(res, j, i_attalign)); @@ -7350,6 +7390,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) { int adnum; + if (tbinfo->attidentity[j] != ' ') + continue; + adnum = atoi(PQgetvalue(res, j, 2)); if (adnum <= 0 || adnum > ntups) @@ -15502,10 +15545,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) /* * DROP must be fully qualified in case same name appears in pg_catalog */ - appendPQExpBuffer(delqry, "DROP SEQUENCE %s.", - fmtId(tbinfo->dobj.namespace->dobj.name)); - appendPQExpBuffer(delqry, "%s;\n", - fmtId(tbinfo->dobj.name)); + if (!tbinfo->is_identity_sequence) + { + appendPQExpBuffer(delqry, "DROP SEQUENCE %s.", + fmtId(tbinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delqry, "%s;\n", + fmtId(tbinfo->dobj.name)); + } resetPQExpBuffer(query); @@ -15517,9 +15563,27 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) tbinfo->dobj.catId.oid); } - appendPQExpBuffer(query, - "CREATE SEQUENCE %s\n", - fmtId(tbinfo->dobj.name)); + if (tbinfo->is_identity_sequence) + { + TableInfo *owning_tab = findTableByOid(tbinfo->owning_tab); + + appendPQExpBuffer(query, + "ALTER TABLE %s ", + fmtId(owning_tab->dobj.name)); + appendPQExpBuffer(query, + "ALTER COLUMN %s ADD GENERATED ", + fmtId(owning_tab->attnames[tbinfo->owning_col - 1])); + if (owning_tab->attidentity[tbinfo->owning_col - 1] == 'a') + appendPQExpBuffer(query, "ALWAYS"); + else if (owning_tab->attidentity[tbinfo->owning_col - 1] == 'd') + appendPQExpBuffer(query, "BY DEFAULT"); + appendPQExpBuffer(query, " AS IDENTITY (\n SEQUENCE NAME %s\n", + fmtId(tbinfo->dobj.name)); + } + else + appendPQExpBuffer(query, + "CREATE SEQUENCE %s\n", + fmtId(tbinfo->dobj.name)); if (fout->remoteVersion >= 80400) appendPQExpBuffer(query, " START WITH %s\n", startv); @@ -15540,7 +15604,10 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) " CACHE %s%s", cache, (cycled ? "\n CYCLE" : "")); - appendPQExpBufferStr(query, ";\n"); + if (tbinfo->is_identity_sequence) + appendPQExpBufferStr(query, "\n);\n"); + else + appendPQExpBufferStr(query, ";\n"); appendPQExpBuffer(labelq, "SEQUENCE %s", fmtId(tbinfo->dobj.name)); @@ -15573,7 +15640,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) * We need not schema-qualify the table reference because both sequence * and table must be in the same schema. */ - if (OidIsValid(tbinfo->owning_tab)) + if (OidIsValid(tbinfo->owning_tab) && !tbinfo->is_identity_sequence) { TableInfo *owning_tab = findTableByOid(tbinfo->owning_tab); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 395a9f3288..315a2fa7a4 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -284,6 +284,7 @@ typedef struct _tableInfo /* these two are set only if table is a sequence owned by a column: */ Oid owning_tab; /* OID of table owning sequence */ int owning_col; /* attr # of column owning sequence */ + bool is_identity_sequence; int relpages; /* table's size in pages (from pg_class) */ bool interesting; /* true if need to collect more data */ @@ -302,6 +303,7 @@ typedef struct _tableInfo char *attstorage; /* attribute storage scheme */ char *typstorage; /* type storage scheme */ bool *attisdropped; /* true if attr is dropped; don't dump it */ + char *attidentity; int *attlen; /* attribute length, used by binary_upgrade */ char *attalign; /* attribute align, used by binary_upgrade */ bool *attislocal; /* true if attr has local definition */ @@ -313,6 +315,7 @@ typedef struct _tableInfo struct _attrDefInfo **attrdefs; /* DEFAULT expressions */ struct _constraintInfo *checkexprs; /* CHECK constraints */ char *partkeydef; /* partition key definition */ + bool needs_override; /* has GENERATED ALWAYS AS IDENTITY */ /* * Stuff computed only for dumpable tables. diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index a582a37953..2f058bddeb 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1582,6 +1582,10 @@ describeOneTableDetails(const char *schemaname, " WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation"); else appendPQExpBufferStr(&buf, "\n NULL AS attcollation"); + if (pset.sversion >= 100000) + appendPQExpBufferStr(&buf, ", a.attidentity"); + else + appendPQExpBufferStr(&buf, ", NULL AS attidentity"); if (tableinfo.relkind == 'i') appendPQExpBufferStr(&buf, ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef"); else @@ -1756,12 +1760,24 @@ describeOneTableDetails(const char *schemaname, /* Collation, Nullable, Default */ if (show_column_details) { + char *identity; + char *default_str = ""; + printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false); printTableAddCell(&cont, strcmp(PQgetvalue(res, i, 3), "t") == 0 ? "not null" : "", false, false); - /* (note: above we cut off the 'default' string at 128) */ - printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false); + identity = PQgetvalue(res, i, 6); + + if (identity[0] == ' ') + /* (note: above we cut off the 'default' string at 128) */ + default_str = PQgetvalue(res, i, 2); + else if (identity[0] == 'a') + default_str = "generated always as identity"; + else if (identity[0] == 'd') + default_str = "generated by default as identity"; + + printTableAddCell(&cont, default_str, false, false); } /* Value: for sequences only */ @@ -1770,16 +1786,16 @@ describeOneTableDetails(const char *schemaname, /* Expression for index column */ if (tableinfo.relkind == 'i') - printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false); /* FDW options for foreign table column, only for 9.2 or later */ if (tableinfo.relkind == 'f' && pset.sversion >= 90200) - printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false); + printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false); /* Storage and Description */ if (verbose) { - int firstvcol = 8; + int firstvcol = 9; char *storage = PQgetvalue(res, i, firstvcol); /* these strings are literal in our syntax, so not translated. */ diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 02c8d60c29..4aaa9add9a 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1807,7 +1807,7 @@ psql_completion(const char *text, int start, int end) /* ALTER TABLE ALTER [COLUMN] <foo> */ else if (Matches6("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) || Matches5("ALTER", "TABLE", MatchAny, "ALTER", MatchAny)) - COMPLETE_WITH_LIST4("TYPE", "SET", "RESET", "DROP"); + COMPLETE_WITH_LIST5("TYPE", "SET", "RESET", "ADD", "DROP"); /* ALTER TABLE ALTER [COLUMN] <foo> SET */ else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") || Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET")) @@ -1823,7 +1823,7 @@ psql_completion(const char *text, int start, int end) /* ALTER TABLE ALTER [COLUMN] <foo> DROP */ else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "DROP") || Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "DROP")) - COMPLETE_WITH_LIST2("DEFAULT", "NOT NULL"); + COMPLETE_WITH_LIST3("DEFAULT", "IDENTITY", "NOT NULL"); else if (Matches4("ALTER", "TABLE", MatchAny, "CLUSTER")) COMPLETE_WITH_CONST("ON"); else if (Matches5("ALTER", "TABLE", MatchAny, "CLUSTER", "ON")) @@ -2769,17 +2769,25 @@ psql_completion(const char *text, int start, int end) /* * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or - * "TABLE" or "DEFAULT VALUES" + * "TABLE" or "DEFAULT VALUES" or "OVERRIDING" */ else if (TailMatches3("INSERT", "INTO", MatchAny)) - COMPLETE_WITH_LIST5("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES"); + COMPLETE_WITH_LIST6("(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", "OVERRIDING"); /* * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or - * "TABLE" + * "TABLE" or "OVERRIDING" */ else if (TailMatches4("INSERT", "INTO", MatchAny, MatchAny) && ends_with(prev_wd, ')')) + COMPLETE_WITH_LIST4("SELECT", "TABLE", "VALUES", "OVERRIDING"); + + /* Complete OVERRIDING */ + else if (TailMatches1("OVERRIDING")) + COMPLETE_WITH_LIST2("SYSTEM VALUE", "USER VALUE"); + + /* Complete after OVERRIDING clause */ + else if (TailMatches3("OVERRIDING", MatchAny, "VALUE")) COMPLETE_WITH_LIST3("SELECT", "TABLE", "VALUES"); /* Insert an open parenthesis after "VALUES" */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 45596abe76..24d9652f9c 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201612231 +#define CATALOG_VERSION_NO 201612235 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index e8a302f2fd..44f7e21f71 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -238,7 +238,7 @@ extern bool sequenceIsOwned(Oid seqId, Oid *tableId, int32 *colId); extern void markSequenceUnowned(Oid seqId); -extern List *getOwnedSequences(Oid relid); +extern List *getOwnedSequences(Oid relid, AttrNumber attnum); extern Oid get_constraint_index(Oid constraintId); diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index 39d8eedb4e..6406b07599 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -133,6 +133,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK /* Has DEFAULT value or not */ bool atthasdef; + /* 'a', 'd', or ' ' */ + char attidentity; + /* Is dropped (ie, logically invisible) or not */ bool attisdropped; @@ -188,7 +191,7 @@ typedef FormData_pg_attribute *Form_pg_attribute; * ---------------- */ -#define Natts_pg_attribute 21 +#define Natts_pg_attribute 22 #define Anum_pg_attribute_attrelid 1 #define Anum_pg_attribute_attname 2 #define Anum_pg_attribute_atttypid 3 @@ -203,13 +206,14 @@ typedef FormData_pg_attribute *Form_pg_attribute; #define Anum_pg_attribute_attalign 12 #define Anum_pg_attribute_attnotnull 13 #define Anum_pg_attribute_atthasdef 14 -#define Anum_pg_attribute_attisdropped 15 -#define Anum_pg_attribute_attislocal 16 -#define Anum_pg_attribute_attinhcount 17 -#define Anum_pg_attribute_attcollation 18 -#define Anum_pg_attribute_attacl 19 -#define Anum_pg_attribute_attoptions 20 -#define Anum_pg_attribute_attfdwoptions 21 +#define Anum_pg_attribute_attidentity 15 +#define Anum_pg_attribute_attisdropped 16 +#define Anum_pg_attribute_attislocal 17 +#define Anum_pg_attribute_attinhcount 18 +#define Anum_pg_attribute_attcollation 19 +#define Anum_pg_attribute_attacl 20 +#define Anum_pg_attribute_attoptions 21 +#define Anum_pg_attribute_attfdwoptions 22 /* ---------------- diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index a61b7a2917..f4a9488483 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -149,7 +149,7 @@ typedef FormData_pg_class *Form_pg_class; */ DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); -DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_)); +DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 22 0 f f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 1fd75b216d..b58ab10c0f 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -58,6 +58,7 @@ extern Datum setval_oid(PG_FUNCTION_ARGS); extern Datum setval3_oid(PG_FUNCTION_ARGS); extern Datum lastval(PG_FUNCTION_ARGS); +extern List *sequence_options(Oid relid); extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS); extern Datum pg_sequence_last_value(PG_FUNCTION_ARGS); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 9d8ef775e4..39e32189a8 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -25,6 +25,13 @@ #include "nodes/primnodes.h" #include "nodes/value.h" +typedef enum OverridingKind +{ + OVERRIDING_NOT_SET = 0, + OVERRIDING_USER_VALUE, + OVERRIDING_SYSTEM_VALUE +} OverridingKind; + /* Possible sources of a Query */ typedef enum QuerySource { @@ -131,6 +138,8 @@ typedef struct Query List *targetList; /* target list (of TargetEntry) */ + OverridingKind override; /* OVERRIDING clause */ + OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */ List *returningList; /* return-values list (of TargetEntry) */ @@ -596,6 +605,7 @@ typedef struct ColumnDef char storage; /* attstorage setting, or 0 for default */ Node *raw_default; /* default value (untransformed parse tree) */ Node *cooked_default; /* default value (transformed expr tree) */ + char identity; /* attidentity setting */ CollateClause *collClause; /* untransformed COLLATE spec, if any */ Oid collOid; /* collation OID (InvalidOid if not set) */ List *constraints; /* other constraints on column */ @@ -617,9 +627,10 @@ typedef enum TableLikeOption { CREATE_TABLE_LIKE_DEFAULTS = 1 << 0, CREATE_TABLE_LIKE_CONSTRAINTS = 1 << 1, - CREATE_TABLE_LIKE_INDEXES = 1 << 2, - CREATE_TABLE_LIKE_STORAGE = 1 << 3, - CREATE_TABLE_LIKE_COMMENTS = 1 << 4, + CREATE_TABLE_LIKE_IDENTITY = 1 << 2, + CREATE_TABLE_LIKE_INDEXES = 1 << 3, + CREATE_TABLE_LIKE_STORAGE = 1 << 4, + CREATE_TABLE_LIKE_COMMENTS = 1 << 5, CREATE_TABLE_LIKE_ALL = PG_INT32_MAX } TableLikeOption; @@ -1328,6 +1339,7 @@ typedef struct InsertStmt OnConflictClause *onConflictClause; /* ON CONFLICT clause */ List *returningList; /* list of expressions to return */ WithClause *withClause; /* WITH clause */ + OverridingKind override; /* OVERRIDING clause */ } InsertStmt; /* ---------------------- @@ -1631,7 +1643,10 @@ typedef enum AlterTableType AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */ AT_GenericOptions, /* OPTIONS (...) */ AT_AttachPartition, /* ATTACH PARTITION */ - AT_DetachPartition /* DETACH PARTITION */ + AT_DetachPartition, /* DETACH PARTITION */ + AT_AddIdentity, /* ADD IDENTITY */ + AT_SetIdentity, /* SET identity column options */ + AT_DropIdentity /* DROP IDENTITY */ } AlterTableType; typedef struct ReplicaIdentityStmt @@ -1904,6 +1919,7 @@ typedef enum ConstrType /* types of constraints */ * expect it */ CONSTR_NOTNULL, CONSTR_DEFAULT, + CONSTR_IDENTITY, CONSTR_CHECK, CONSTR_PRIMARY, CONSTR_UNIQUE, @@ -1915,6 +1931,9 @@ typedef enum ConstrType /* types of constraints */ CONSTR_ATTR_IMMEDIATE } ConstrType; +#define CONSTR_GENERATED_ALWAYS 'a' +#define CONSTR_GENERATED_DEFAULT 'd' + /* Foreign key action codes */ #define FKCONSTR_ACTION_NOACTION 'a' #define FKCONSTR_ACTION_RESTRICT 'r' @@ -1942,6 +1961,7 @@ typedef struct Constraint bool is_no_inherit; /* is constraint non-inheritable? */ Node *raw_expr; /* expr, as untransformed parse tree */ char *cooked_expr; /* expr, as nodeToString representation */ + char generated_when; /* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */ List *keys; /* String nodes naming referenced column(s) */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 581ff6eedb..d522e89efe 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -173,6 +173,7 @@ PG_KEYWORD("from", FROM, RESERVED_KEYWORD) PG_KEYWORD("full", FULL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("function", FUNCTION, UNRESERVED_KEYWORD) PG_KEYWORD("functions", FUNCTIONS, UNRESERVED_KEYWORD) +PG_KEYWORD("generated", GENERATED, UNRESERVED_KEYWORD) PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD) PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD) PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD) @@ -285,6 +286,7 @@ PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD) PG_KEYWORD("overlaps", OVERLAPS, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("overlay", OVERLAY, COL_NAME_KEYWORD) +PG_KEYWORD("overriding", OVERRIDING, UNRESERVED_KEYWORD) PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD) PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD) PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD) diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index dcb89807e9..573533b389 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -64,6 +64,7 @@ extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype, extern char *get_attname(Oid relid, AttrNumber attnum); extern char *get_relid_attribute_name(Oid relid, AttrNumber attnum); extern AttrNumber get_attnum(Oid relid, const char *attname); +extern char get_attidentity(Oid relid, AttrNumber attnum); extern Oid get_atttype(Oid relid, AttrNumber attnum); extern int32 get_atttypmod(Oid relid, AttrNumber attnum); extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum, diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index a25b221703..3f405c94ce 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -66,6 +66,53 @@ SELECT * FROM inhg; /* Two records with three columns in order x=x, xx=text, y=y (2 rows) DROP TABLE inhg; +CREATE TABLE test_like_id_1 (a int GENERATED ALWAYS AS IDENTITY, b text); +\d test_like_id_1 + Table "public.test_like_id_1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------ + a | integer | | not null | generated always as identity + b | text | | | + +INSERT INTO test_like_id_1 (b) VALUES ('b1'); +SELECT * FROM test_like_id_1; + a | b +---+---- + 1 | b1 +(1 row) + +CREATE TABLE test_like_id_2 (LIKE test_like_id_1); +\d test_like_id_2 + Table "public.test_like_id_2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | not null | + b | text | | | + +INSERT INTO test_like_id_2 (b) VALUES ('b2'); +ERROR: null value in column "a" violates not-null constraint +DETAIL: Failing row contains (null, b2). +SELECT * FROM test_like_id_2; -- identity was not copied + a | b +---+--- +(0 rows) + +CREATE TABLE test_like_id_3 (LIKE test_like_id_1 INCLUDING IDENTITY); +\d test_like_id_3 + Table "public.test_like_id_3" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------ + a | integer | | not null | generated always as identity + b | text | | | + +INSERT INTO test_like_id_3 (b) VALUES ('b3'); +SELECT * FROM test_like_id_3; -- identity was copied and applied + a | b +---+---- + 1 | b3 +(1 row) + +DROP TABLE test_like_id_1, test_like_id_2, test_like_id_3; CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */ INSERT INTO inhg VALUES (5, 10); INSERT INTO inhg VALUES (20, 10); -- should fail diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out new file mode 100644 index 0000000000..4b09a34e3a --- /dev/null +++ b/src/test/regress/expected/identity.out @@ -0,0 +1,274 @@ +CREATE TABLE itest1 (a int generated by default as identity, b text); +CREATE TABLE itest2 (a bigint generated always as identity, b text); +CREATE TABLE itest3 (a smallint generated by default as identity (start with 7 increment by 5), b text); +ALTER TABLE itest3 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error +ERROR: column "a" of relation "itest3" is already an identity column +SELECT table_name, column_name, column_default, is_nullable, is_identity, identity_generation, identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle FROM information_schema.columns WHERE table_name LIKE 'itest_' ORDER BY 1, 2; + table_name | column_name | column_default | is_nullable | is_identity | identity_generation | identity_start | identity_increment | identity_maximum | identity_minimum | identity_cycle +------------+-------------+----------------+-------------+-------------+---------------------+----------------+--------------------+---------------------+------------------+---------------- + itest1 | a | | NO | YES | BY DEFAULT | 1 | 1 | 9223372036854775807 | 1 | NO + itest1 | b | | YES | NO | | | | | | NO + itest2 | a | | NO | YES | ALWAYS | 1 | 1 | 9223372036854775807 | 1 | NO + itest2 | b | | YES | NO | | | | | | NO + itest3 | a | | NO | YES | BY DEFAULT | 7 | 5 | 9223372036854775807 | 1 | NO + itest3 | b | | YES | NO | | | | | | NO +(6 rows) + +-- internal sequences should not be shown here +SELECT sequence_name FROM information_schema.sequences WHERE sequence_name LIKE 'itest%'; + sequence_name +--------------- +(0 rows) + +CREATE TABLE itest4 (a int, b text); +ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, requires NOT NULL +ERROR: column "a" of relation "itest4" must be declared NOT NULL before identity can be added +ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL; +ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- ok +ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, already set +ERROR: column "a" of relation "itest4" is already an identity column +ALTER TABLE itest4 ALTER COLUMN b ADD GENERATED ALWAYS AS IDENTITY; -- error, wrong data type +ERROR: identity column type must be smallint, integer, or bigint +-- for later +ALTER TABLE itest4 ALTER COLUMN b SET DEFAULT ''; +-- invalid column type +CREATE TABLE itest_err_1 (a text generated by default as identity); +ERROR: identity column type must be smallint, integer, or bigint +-- duplicate identity +CREATE TABLE itest_err_2 (a int generated always as identity generated by default as identity); +ERROR: multiple identity specifications for column "a" of table "itest_err_2" +LINE 1: ...E itest_err_2 (a int generated always as identity generated ... + ^ +-- cannot have default and identity +CREATE TABLE itest_err_3 (a int default 5 generated by default as identity); +ERROR: both default and identity specified for column "a" of table "itest_err_3" +LINE 1: CREATE TABLE itest_err_3 (a int default 5 generated by defau... + ^ +-- cannot combine serial and identity +CREATE TABLE itest_err_4 (a serial generated by default as identity); +ERROR: both default and identity specified for column "a" of table "itest_err_4" +INSERT INTO itest1 DEFAULT VALUES; +INSERT INTO itest1 DEFAULT VALUES; +INSERT INTO itest2 DEFAULT VALUES; +INSERT INTO itest2 DEFAULT VALUES; +INSERT INTO itest3 DEFAULT VALUES; +INSERT INTO itest3 DEFAULT VALUES; +INSERT INTO itest4 DEFAULT VALUES; +INSERT INTO itest4 DEFAULT VALUES; +SELECT * FROM itest1; + a | b +---+--- + 1 | + 2 | +(2 rows) + +SELECT * FROM itest2; + a | b +---+--- + 1 | + 2 | +(2 rows) + +SELECT * FROM itest3; + a | b +----+--- + 7 | + 12 | +(2 rows) + +SELECT * FROM itest4; + a | b +---+--- + 1 | + 2 | +(2 rows) + +-- OVERRIDING tests +INSERT INTO itest1 VALUES (10, 'xyz'); +INSERT INTO itest1 OVERRIDING USER VALUE VALUES (10, 'xyz'); +SELECT * FROM itest1; + a | b +----+----- + 1 | + 2 | + 10 | xyz + 3 | xyz +(4 rows) + +INSERT INTO itest2 VALUES (10, 'xyz'); +ERROR: cannot insert into column "a" +DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. +HINT: Use OVERRIDING SYSTEM VALUE to override. +INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (10, 'xyz'); +SELECT * FROM itest2; + a | b +----+----- + 1 | + 2 | + 10 | xyz +(3 rows) + +-- UPDATE tests +UPDATE itest1 SET a = 101 WHERE a = 1; +UPDATE itest1 SET a = DEFAULT WHERE a = 2; +SELECT * FROM itest1; + a | b +-----+----- + 10 | xyz + 3 | xyz + 101 | + 4 | +(4 rows) + +UPDATE itest2 SET a = 101 WHERE a = 1; +ERROR: column "a" can only be updated to DEFAULT +DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. +UPDATE itest2 SET a = DEFAULT WHERE a = 2; +SELECT * FROM itest2; + a | b +----+----- + 1 | + 10 | xyz + 3 | +(3 rows) + +-- DROP IDENTITY tests +ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY; +ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY; -- error +ERROR: column "a" of relation "itest4" is not an identity column +INSERT INTO itest4 DEFAULT VALUES; -- fails because NOT NULL is not dropped +ERROR: null value in column "a" violates not-null constraint +DETAIL: Failing row contains (null, ). +ALTER TABLE itest4 ALTER COLUMN a DROP NOT NULL; +INSERT INTO itest4 DEFAULT VALUES; +SELECT * FROM itest4; + a | b +---+--- + 1 | + 2 | + | +(3 rows) + +-- check that sequence is removed +SELECT sequence_name FROM itest4_a_seq; +ERROR: relation "itest4_a_seq" does not exist +LINE 1: SELECT sequence_name FROM itest4_a_seq; + ^ +-- test views +CREATE TABLE itest10 (a int generated by default as identity, b text); +CREATE TABLE itest11 (a int generated always as identity, b text); +CREATE VIEW itestv10 AS SELECT * FROM itest10; +CREATE VIEW itestv11 AS SELECT * FROM itest11; +INSERT INTO itestv10 DEFAULT VALUES; +INSERT INTO itestv10 DEFAULT VALUES; +INSERT INTO itestv11 DEFAULT VALUES; +INSERT INTO itestv11 DEFAULT VALUES; +SELECT * FROM itestv10; + a | b +---+--- + 1 | + 2 | +(2 rows) + +SELECT * FROM itestv11; + a | b +---+--- + 1 | + 2 | +(2 rows) + +INSERT INTO itestv10 VALUES (10, 'xyz'); +INSERT INTO itestv10 OVERRIDING USER VALUE VALUES (11, 'xyz'); +SELECT * FROM itestv10; + a | b +----+----- + 1 | + 2 | + 10 | xyz + 3 | xyz +(4 rows) + +INSERT INTO itestv11 VALUES (10, 'xyz'); +ERROR: cannot insert into column "a" +DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. +HINT: Use OVERRIDING SYSTEM VALUE to override. +INSERT INTO itestv11 OVERRIDING SYSTEM VALUE VALUES (11, 'xyz'); +SELECT * FROM itestv11; + a | b +----+----- + 1 | + 2 | + 11 | xyz +(3 rows) + +-- various tests +-- fail, not allowed for identity columns +ALTER TABLE itest1 ALTER COLUMN a SET DEFAULT 1; +ERROR: column "a" of relation "itest1" is an identity column +-- fail, not allowed, already has a default +CREATE TABLE itest5 (a serial, b text); +ALTER TABLE itest5 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; +ERROR: column "a" of relation "itest5" already has a default value +-- not allowed for now +ALTER TABLE itest3 ALTER COLUMN a TYPE text; +ERROR: cannot alter data type of identity column "a" +-- ALTER COLUMN +CREATE TABLE itest6 (a int GENERATED ALWAYS AS IDENTITY, b text); +INSERT INTO itest6 DEFAULT VALUES; +ALTER TABLE itest6 ALTER COLUMN a SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 100 RESTART; +INSERT INTO itest6 DEFAULT VALUES; +INSERT INTO itest6 DEFAULT VALUES; +SELECT * FROM itest6; + a | b +-----+--- + 1 | + 100 | + 102 | +(3 rows) + +SELECT table_name, column_name, is_identity, identity_generation FROM information_schema.columns WHERE table_name = 'itest6'; + table_name | column_name | is_identity | identity_generation +------------+-------------+-------------+--------------------- + itest6 | a | YES | BY DEFAULT + itest6 | b | NO | +(2 rows) + +ALTER TABLE itest6 ALTER COLUMN b SET INCREMENT BY 2; -- fail, not identity +ERROR: column "b" of relation "itest6" is not an identity column +-- inheritance +CREATE TABLE itest7 (a int GENERATED ALWAYS AS IDENTITY); +INSERT INTO itest7 DEFAULT VALUES; +SELECT * FROM itest7; + a +--- + 1 +(1 row) + +-- inherit identity column not allowed +CREATE TABLE itest7a (b text) INHERITS (itest7); +ERROR: cannot inherit from table with identity column +-- make column identity in child table +CREATE TABLE itest7b (a int); +CREATE TABLE itest7c (a int GENERATED ALWAYS AS IDENTITY) INHERITS (itest7b); +NOTICE: merging column "a" with inherited definition +INSERT INTO itest7c DEFAULT VALUES; +SELECT * FROM itest7c; + a +--- + 1 +(1 row) + +SELECT table_name, column_name, is_nullable, is_identity, identity_generation FROM information_schema.columns WHERE table_name LIKE 'itest7%' ORDER BY 1, 2; + table_name | column_name | is_nullable | is_identity | identity_generation +------------+-------------+-------------+-------------+--------------------- + itest7 | a | NO | YES | ALWAYS + itest7b | a | YES | NO | + itest7c | a | NO | YES | ALWAYS +(3 rows) + +CREATE TABLE itest7d (a int); +CREATE TABLE itest7e () INHERITS (itest7d); +ALTER TABLE itest7d ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- fail +ERROR: cannot add identity to table that has child tables +ALTER TABLE itest7d ADD COLUMN b int GENERATED ALWAYS AS IDENTITY; -- fail +ERROR: cannot add identity column to table that has child tables diff --git a/src/test/regress/expected/truncate.out b/src/test/regress/expected/truncate.out index 5c5277e0f1..c76a21788f 100644 --- a/src/test/regress/expected/truncate.out +++ b/src/test/regress/expected/truncate.out @@ -393,6 +393,36 @@ SELECT * FROM truncate_a; 2 | 34 (2 rows) +CREATE TABLE truncate_b (id int GENERATED ALWAYS AS IDENTITY (START WITH 44)); +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + id +---- + 44 + 45 +(2 rows) + +TRUNCATE truncate_b; +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + id +---- + 46 + 47 +(2 rows) + +TRUNCATE truncate_b RESTART IDENTITY; +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + id +---- + 44 + 45 +(2 rows) + -- check rollback of a RESTART IDENTITY operation BEGIN; TRUNCATE truncate_a RESTART IDENTITY; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 8641769351..9ff4bd4357 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -108,6 +108,11 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # ---------- test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +# ---------- +# Another group of parallel tests +# ---------- +test: identity + # event triggers cannot run concurrently with any test that runs DDL test: event_trigger diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 835cf3556c..1f022aef94 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -161,6 +161,7 @@ test: conversion test: truncate test: alter_table test: sequence +test: identity test: polymorphism test: rowtypes test: returning diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index 900ca804cb..557040bbe7 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -37,6 +37,20 @@ CREATE TABLE inhg (x text, LIKE inhx INCLUDING CONSTRAINTS, y text); /* Copies c SELECT * FROM inhg; /* Two records with three columns in order x=x, xx=text, y=y */ DROP TABLE inhg; +CREATE TABLE test_like_id_1 (a int GENERATED ALWAYS AS IDENTITY, b text); +\d test_like_id_1 +INSERT INTO test_like_id_1 (b) VALUES ('b1'); +SELECT * FROM test_like_id_1; +CREATE TABLE test_like_id_2 (LIKE test_like_id_1); +\d test_like_id_2 +INSERT INTO test_like_id_2 (b) VALUES ('b2'); +SELECT * FROM test_like_id_2; -- identity was not copied +CREATE TABLE test_like_id_3 (LIKE test_like_id_1 INCLUDING IDENTITY); +\d test_like_id_3 +INSERT INTO test_like_id_3 (b) VALUES ('b3'); +SELECT * FROM test_like_id_3; -- identity was copied and applied +DROP TABLE test_like_id_1, test_like_id_2, test_like_id_3; + CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */ INSERT INTO inhg VALUES (5, 10); INSERT INTO inhg VALUES (20, 10); -- should fail diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql new file mode 100644 index 0000000000..9bced8dc4c --- /dev/null +++ b/src/test/regress/sql/identity.sql @@ -0,0 +1,162 @@ +CREATE TABLE itest1 (a int generated by default as identity, b text); +CREATE TABLE itest2 (a bigint generated always as identity, b text); +CREATE TABLE itest3 (a smallint generated by default as identity (start with 7 increment by 5), b text); +ALTER TABLE itest3 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error + +SELECT table_name, column_name, column_default, is_nullable, is_identity, identity_generation, identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle FROM information_schema.columns WHERE table_name LIKE 'itest_' ORDER BY 1, 2; + +-- internal sequences should not be shown here +SELECT sequence_name FROM information_schema.sequences WHERE sequence_name LIKE 'itest%'; + +CREATE TABLE itest4 (a int, b text); +ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, requires NOT NULL +ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL; +ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- ok +ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, already set +ALTER TABLE itest4 ALTER COLUMN b ADD GENERATED ALWAYS AS IDENTITY; -- error, wrong data type + +-- for later +ALTER TABLE itest4 ALTER COLUMN b SET DEFAULT ''; + +-- invalid column type +CREATE TABLE itest_err_1 (a text generated by default as identity); + +-- duplicate identity +CREATE TABLE itest_err_2 (a int generated always as identity generated by default as identity); + +-- cannot have default and identity +CREATE TABLE itest_err_3 (a int default 5 generated by default as identity); + +-- cannot combine serial and identity +CREATE TABLE itest_err_4 (a serial generated by default as identity); + +INSERT INTO itest1 DEFAULT VALUES; +INSERT INTO itest1 DEFAULT VALUES; +INSERT INTO itest2 DEFAULT VALUES; +INSERT INTO itest2 DEFAULT VALUES; +INSERT INTO itest3 DEFAULT VALUES; +INSERT INTO itest3 DEFAULT VALUES; +INSERT INTO itest4 DEFAULT VALUES; +INSERT INTO itest4 DEFAULT VALUES; + +SELECT * FROM itest1; +SELECT * FROM itest2; +SELECT * FROM itest3; +SELECT * FROM itest4; + + +-- OVERRIDING tests + +INSERT INTO itest1 VALUES (10, 'xyz'); +INSERT INTO itest1 OVERRIDING USER VALUE VALUES (10, 'xyz'); + +SELECT * FROM itest1; + +INSERT INTO itest2 VALUES (10, 'xyz'); +INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (10, 'xyz'); + +SELECT * FROM itest2; + + +-- UPDATE tests + +UPDATE itest1 SET a = 101 WHERE a = 1; +UPDATE itest1 SET a = DEFAULT WHERE a = 2; +SELECT * FROM itest1; + +UPDATE itest2 SET a = 101 WHERE a = 1; +UPDATE itest2 SET a = DEFAULT WHERE a = 2; +SELECT * FROM itest2; + + +-- DROP IDENTITY tests + +ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY; +ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY; -- error + +INSERT INTO itest4 DEFAULT VALUES; -- fails because NOT NULL is not dropped +ALTER TABLE itest4 ALTER COLUMN a DROP NOT NULL; +INSERT INTO itest4 DEFAULT VALUES; +SELECT * FROM itest4; + +-- check that sequence is removed +SELECT sequence_name FROM itest4_a_seq; + + +-- test views + +CREATE TABLE itest10 (a int generated by default as identity, b text); +CREATE TABLE itest11 (a int generated always as identity, b text); + +CREATE VIEW itestv10 AS SELECT * FROM itest10; +CREATE VIEW itestv11 AS SELECT * FROM itest11; + +INSERT INTO itestv10 DEFAULT VALUES; +INSERT INTO itestv10 DEFAULT VALUES; + +INSERT INTO itestv11 DEFAULT VALUES; +INSERT INTO itestv11 DEFAULT VALUES; + +SELECT * FROM itestv10; +SELECT * FROM itestv11; + +INSERT INTO itestv10 VALUES (10, 'xyz'); +INSERT INTO itestv10 OVERRIDING USER VALUE VALUES (11, 'xyz'); + +SELECT * FROM itestv10; + +INSERT INTO itestv11 VALUES (10, 'xyz'); +INSERT INTO itestv11 OVERRIDING SYSTEM VALUE VALUES (11, 'xyz'); + +SELECT * FROM itestv11; + + +-- various tests + +-- fail, not allowed for identity columns +ALTER TABLE itest1 ALTER COLUMN a SET DEFAULT 1; + +-- fail, not allowed, already has a default +CREATE TABLE itest5 (a serial, b text); +ALTER TABLE itest5 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; + +-- not allowed for now +ALTER TABLE itest3 ALTER COLUMN a TYPE text; + + +-- ALTER COLUMN + +CREATE TABLE itest6 (a int GENERATED ALWAYS AS IDENTITY, b text); +INSERT INTO itest6 DEFAULT VALUES; + +ALTER TABLE itest6 ALTER COLUMN a SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 100 RESTART; +INSERT INTO itest6 DEFAULT VALUES; +INSERT INTO itest6 DEFAULT VALUES; +SELECT * FROM itest6; + +SELECT table_name, column_name, is_identity, identity_generation FROM information_schema.columns WHERE table_name = 'itest6'; + +ALTER TABLE itest6 ALTER COLUMN b SET INCREMENT BY 2; -- fail, not identity + + +-- inheritance + +CREATE TABLE itest7 (a int GENERATED ALWAYS AS IDENTITY); +INSERT INTO itest7 DEFAULT VALUES; +SELECT * FROM itest7; + +-- inherit identity column not allowed +CREATE TABLE itest7a (b text) INHERITS (itest7); + +-- make column identity in child table +CREATE TABLE itest7b (a int); +CREATE TABLE itest7c (a int GENERATED ALWAYS AS IDENTITY) INHERITS (itest7b); +INSERT INTO itest7c DEFAULT VALUES; +SELECT * FROM itest7c; + +SELECT table_name, column_name, is_nullable, is_identity, identity_generation FROM information_schema.columns WHERE table_name LIKE 'itest7%' ORDER BY 1, 2; + +CREATE TABLE itest7d (a int); +CREATE TABLE itest7e () INHERITS (itest7d); +ALTER TABLE itest7d ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- fail +ALTER TABLE itest7d ADD COLUMN b int GENERATED ALWAYS AS IDENTITY; -- fail diff --git a/src/test/regress/sql/truncate.sql b/src/test/regress/sql/truncate.sql index a3d6f5368f..1aab021dbb 100644 --- a/src/test/regress/sql/truncate.sql +++ b/src/test/regress/sql/truncate.sql @@ -202,6 +202,24 @@ CREATE TABLE truncate_a (id serial, INSERT INTO truncate_a DEFAULT VALUES; SELECT * FROM truncate_a; +CREATE TABLE truncate_b (id int GENERATED ALWAYS AS IDENTITY (START WITH 44)); + +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + +TRUNCATE truncate_b; + +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + +TRUNCATE truncate_b RESTART IDENTITY; + +INSERT INTO truncate_b DEFAULT VALUES; +INSERT INTO truncate_b DEFAULT VALUES; +SELECT * FROM truncate_b; + -- check rollback of a RESTART IDENTITY operation BEGIN; TRUNCATE truncate_a RESTART IDENTITY; -- 2.11.0 --------------E3ECD3B33508773AAC55B542 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list ([email protected]) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --------------E3ECD3B33508773AAC55B542-- ^ permalink raw reply [nested|flat] 75+ messages in thread
* strange error sequence on parallel btree creation @ 2019-01-30 10:26 Alvaro Herrera <[email protected]> 2019-01-30 17:24 ` Re: strange error sequence on parallel btree creation Peter Geoghegan <[email protected]> 0 siblings, 1 reply; 75+ messages in thread From: Alvaro Herrera @ 2019-01-30 10:26 UTC (permalink / raw) To: Pg Hackers <[email protected]>; Peter Geoghegan <[email protected]> Hi While trying out the progress report mechanism for btrees, I noticed this strange chain of errors: 2019-01-29 15:51:55.928 -03 [43789] ERROR: no se pudo crear el índice único «a_generate_series_idx» 2019-01-29 15:51:55.928 -03 [43789] DETALLE: La llave (generate_series)=(152) está duplicada. 2019-01-29 15:51:55.928 -03 [43789] SENTENCIA: create unique index concurrently on a (generate_series); 2019-01-29 15:51:55.928 -03 [44634] ERROR: no se pudo crear el índice único «a_generate_series_idx» 2019-01-29 15:51:55.928 -03 [44634] DETALLE: La llave (generate_series)=(31339) está duplicada. 2019-01-29 15:51:55.928 -03 [44634] SENTENCIA: create unique index concurrently on a (generate_series); 2019-01-29 15:51:55.985 -03 [43670] LOG: background worker "parallel worker" (PID 44634) terminó con código de salida 1 Note that those come from the same create index: the one on process 46299 must evidently be a parallel worker. It's weird that two processes report the index building error. But even if it were correct, the CONTEXT line in the other process is not okay ... precisely because it's the parent. What I did was create table a as select * from generate_series(1, 1000000); insert into a select * from generate_series(1, 80000000); create index on a (generate_series); The last command used the laptop disk, excessive use of which cause the whole thing to stall for a few dozen seconds (I think it's because of the encryption but I'm not sure). I then changed lc_messages to C, for pasting here, and repeated with an external USB drive -- result: it fails cleanly (only one ERROR). -- Álvaro Herrera ^ permalink raw reply [nested|flat] 75+ messages in thread
* Re: strange error sequence on parallel btree creation 2019-01-30 10:26 strange error sequence on parallel btree creation Alvaro Herrera <[email protected]> @ 2019-01-30 17:24 ` Peter Geoghegan <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Peter Geoghegan @ 2019-01-30 17:24 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: Pg Hackers <[email protected]> Hi Álvaro, On Wed, Jan 30, 2019 at 5:40 AM Alvaro Herrera <[email protected]> wrote: > Note that those come from the same create index: the one on process > 46299 must evidently be a parallel worker. It's weird that two > processes report the index building error. But even if it were correct, > the CONTEXT line in the other process is not okay ... precisely because > it's the parent. > > What I did was > > create table a as select * from generate_series(1, 1000000); > insert into a select * from generate_series(1, 80000000); > create index on a (generate_series); I can see why you'd find that slightly confusing. I'm not sure what can be done about this scenario specifically, though. It seems to come down to how the parallel infrastructure works, which is not something that I gave much input on. Fundamentally, the parallel infrastructure wants to propagate all errors that it received before parallel workers were shut down. I think that that's probably the right thing to do. I'm not sure what you mean by "But even if it were correct, the CONTEXT line in the other process is not okay ... precisely because it's the parent". Perhaps you can go into more detail on that. The CONTEXT looks like it would look regardless of this race. In any case, I think that the chances of this happening in production are pretty slim. The error messages each refer to specific, distinct pairs of duplicates (duplicated values). It's probably necessary to have an enormous number of duplicates for things to work out this way. That's hardly typical. -- Peter Geoghegan ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v3 1/4] Introduce a new syntax to add/drop publications @ 2021-01-26 07:43 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-01-26 07:43 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 121 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 143 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..88fa7f1b3f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, TupleDesc tupledesc, + List *publications, bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,51 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, RelationGetDescr(rel), + stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1325,77 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, TupleDesc tupledesc, + List *newpublist, bool isadd) +{ + int i; + int npublications; + Datum *publications; + bool nulls[Natts_pg_subscription]; + Datum values[Natts_pg_subscription]; + List *publist = NIL; + ListCell *lc; + ArrayType *array; + + /* deconstruct the subpublications */ + heap_deform_tuple(tuple, tupledesc, values, nulls); + array = DatumGetArrayTypeP(values[Anum_pg_subscription_subpublications - 1]); + deconstruct_array(array, TEXTOID, -1, false, TYPALIGN_INT, + &publications, NULL, &npublications); + + for (i = 0; i < npublications; i++) + publist = lappend(publist, + makeString(TextDatumGetCString((publications[i])))); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" do not in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 7574d545e0..d20e513518 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9615,6 +9615,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index dc2bb40926..9148ca9888 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3553,6 +3553,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v3-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v4 2/5] Introduce a new syntax to add/drop publications @ 2021-02-05 12:59 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-05 12:59 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 113 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 +++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 135 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 082f7855b8..7f296f76a8 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -46,6 +46,8 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *publications, + bool addpub); /* * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands. @@ -857,6 +859,50 @@ AlterSubscription(AlterSubscriptionStmt *stmt) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1278,3 +1324,70 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications) return tablelist; } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool isadd) +{ + Datum datum; + bool isnull; + List *publist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (isadd) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" is already in subscription", + name))); + } + else + { + publist = list_delete_cell(publist, cell); + break; + } + } + } + + if (isadd) + publist = lappend(publist, makeString(name)); + else if (cell == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" does not exists in subscription", + name))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..77b28fc0cf 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9619,6 +9619,26 @@ AlterSubscriptionStmt: n->options = $7; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name ENABLE_P { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..8021f66595 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3581,6 +3581,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.30.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v4-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v5 2/5] Introduce a new syntax to add/drop publications @ 2021-02-13 05:11 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-02-13 05:11 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 144 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 166 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 5cf874e0b4..ad271deb64 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -962,6 +963,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_subpublications(tup, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1546,3 +1594,99 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge ccurrent subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If isadd == true, we will add the list of publications into current + * subscription's publications. Otherwise, we will delete the list of + * publications from current subscription's publications. + */ +static List * +merge_subpublications(HeapTuple tuple, List *newpublist, bool addpub) +{ + Datum datum; + bool isnull; + List *publist = NIL; + List *errlist = NIL; + ListCell *lc; + + /* Get publications */ + datum = SysCacheGetAttr(SUBSCRIPTIONOID, + tuple, + Anum_pg_subscription_subpublications, + &isnull); + Assert(!isnull); + publist = textarray_to_stringlist(DatumGetArrayTypeP(datum)); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, publist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + errlist = lappend(errlist, makeString(name)); + else + publist = list_delete_cell(publist, cell); + break; + } + } + + if (addpub && cell == NULL) + publist = lappend(publist, makeString(name)); + else if (!addpub && cell == NULL) + errlist = lappend(errlist, makeString(name)); + } + + if (errlist != NIL) + { + StringInfoData errstr; + bool first = true; + int len = list_length(errlist); + + initStringInfo(&errstr); + if (len == 1) + appendStringInfo(&errstr, "publication name"); + else + appendStringInfo(&errstr, "publication names"); + + appendStringInfoString(&errstr, " \""); + foreach(lc, errlist) + { + char *name = strVal(lfirst(lc)); + + if (first) + appendStringInfo(&errstr, "%s", name); + else + appendStringInfo(&errstr, ", %s", name); + + first = false; + } + appendStringInfoChar(&errstr, '"'); + + if (addpub) + appendStringInfo(&errstr, " %s already in subscription", + len == 1 ? "is" : "are"); + else + appendStringInfo(&errstr, " %s no exist in subscription", + len == 1 ? "does" : "do"); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", errstr.data))); + } + + if (publist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return publist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index dd72a9fc3c..e45f98d353 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v5-0003-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v8 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 153 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 175 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..368ee36961 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,108 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *plc; + + /* Check for duplicates. */ + foreach(plc, newpublist) + { + char *pname = strVal(lfirst(plc)); + + if (plc == lc) + break; + + if (strcmp(name, pname) == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("publication name \"%s\" used more than once", + pname))); + } + } + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index bc43641ffe..ab0468520c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9640,6 +9640,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 68425eb2c0..83cb7d9fe4 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3593,6 +3593,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v8-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH v7 1/4] Introduce a new syntax to add/drop publications @ 2021-03-07 12:56 Japin Li <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Japin Li @ 2021-03-07 12:56 UTC (permalink / raw) At present, if we want to update publications in subscription, we can use SET PUBLICATION, however, it requires supply all publications that exists and the new publications if we want to add new publications, it's inconvenient. The new syntax only supply the new publications. When the refresh is true, it only refresh the new publications. --- src/backend/commands/subscriptioncmds.c | 132 ++++++++++++++++++++++++ src/backend/parser/gram.y | 20 ++++ src/include/nodes/parsenodes.h | 2 + 3 files changed, 154 insertions(+) diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index bfd3514546..bf1e579e6f 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -47,6 +47,7 @@ #include "utils/syscache.h" static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); +static List *merge_publications(List *oldpublist, List *newpublist, bool addpub); static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err); @@ -964,6 +965,53 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) break; } + case ALTER_SUBSCRIPTION_ADD_PUBLICATION: + case ALTER_SUBSCRIPTION_DROP_PUBLICATION: + { + bool copy_data = false; + bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION; + bool refresh; + List *publist = NIL; + + publist = merge_publications(sub->publications, stmt->publication, isadd); + + parse_subscription_options(stmt->options, + NULL, /* no "connect" */ + NULL, NULL, /* no "enabled" */ + NULL, /* no "create_slot" */ + NULL, NULL, /* no "slot_name" */ + isadd ? ©_data : NULL, /* for drop, no "copy_data" */ + NULL, /* no "synchronous_commit" */ + &refresh, + NULL, NULL, /* no "binary" */ + NULL, NULL); /* no "streaming" */ + + values[Anum_pg_subscription_subpublications - 1] = + publicationListToArray(publist); + replaces[Anum_pg_subscription_subpublications - 1] = true; + + update_tuple = true; + + /* Refresh if user asked us to. */ + if (refresh) + { + if (!sub->enabled) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); + + /* Only refresh the added/dropped list of publications. */ + sub->publications = stmt->publication; + + AlterSubscription_refresh(sub, copy_data); + } + + break; + } + case ALTER_SUBSCRIPTION_REFRESH: { bool copy_data; @@ -1551,3 +1599,87 @@ ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err) errhint("Use %s to disassociate the subscription from the slot.", "ALTER SUBSCRIPTION ... SET (slot_name = NONE)"))); } + +/* + * Merge current subscription's publications and user specified publications + * by ADD/DROP PUBLICATIONS. + * + * If addpub is true, we will add the list of publications into oldpublist. + * Otherwise, we will delete the list of publications from oldpublist. + */ +static List * +merge_publications(List *oldpublist, List *newpublist, bool addpub) +{ + StringInfoData errstr; + int errstrcnt = 0; + ListCell *lc; + + initStringInfo(&errstr); + + foreach(lc, newpublist) + { + char *name = strVal(lfirst(lc)); + ListCell *cell = NULL; + + foreach(cell, oldpublist) + { + char *pubname = strVal(lfirst(cell)); + + if (strcmp(name, pubname) == 0) + { + if (addpub) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + else + oldpublist = list_delete_cell(oldpublist, cell); + + break; + } + } + + if (addpub && cell == NULL) + oldpublist = lappend(oldpublist, makeString(name)); + else if (!addpub && cell == NULL) + { + errstrcnt++; + + if (errstrcnt == 1) + appendStringInfo(&errstr, _("\"%s\""), name); + else + appendStringInfo(&errstr, _(", \"%s\""), name); + } + } + + if (errstrcnt >= 1) + { + if (addpub) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s is already present in the subscription", + "publications %s are already present in the subscription", + errstrcnt, errstr.data))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("publication %s doesn't exist in the subscription", + "publications %s do not exist in the subscription", + errstrcnt, errstr.data))); + } + } + + if (oldpublist == NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subscription must contain at least one publication"))); + + return oldpublist; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 652be0b96d..6d75e82096 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -9609,6 +9609,26 @@ AlterSubscriptionStmt: n->options = $6; $$ = (Node *)n; } + | ALTER SUBSCRIPTION name ADD_P PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_ADD_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } + | ALTER SUBSCRIPTION name DROP PUBLICATION name_list opt_definition + { + AlterSubscriptionStmt *n = + makeNode(AlterSubscriptionStmt); + n->kind = ALTER_SUBSCRIPTION_DROP_PUBLICATION; + n->subname = $3; + n->publication = $6; + n->options = $7; + $$ = (Node *)n; + } | ALTER SUBSCRIPTION name SET PUBLICATION name_list opt_definition { AlterSubscriptionStmt *n = diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 236832a2ca..e109607936 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3580,6 +3580,8 @@ typedef enum AlterSubscriptionType ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_PUBLICATION, + ALTER_SUBSCRIPTION_ADD_PUBLICATION, + ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_ENABLED } AlterSubscriptionType; -- 2.25.1 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v7-0002-Test-ALTER-SUBSCRIPTION-.-ADD-DROP-PUBLICATION.patch ^ permalink raw reply [nested|flat] 75+ messages in thread
* Fix use of openssl.path() if openssl isn't found @ 2023-11-07 22:06 Tristan Partin <[email protected]> 2023-11-08 05:53 ` Re: Fix use of openssl.path() if openssl isn't found Michael Paquier <[email protected]> 0 siblings, 1 reply; 75+ messages in thread From: Tristan Partin @ 2023-11-07 22:06 UTC (permalink / raw) To: pgsql-hackers Found this issue during my Fedora 39 upgrade. Tested that uninstalling openssl still allows the various ssl tests to run and succeed. -- Tristan Partin Neon (https://neon.tech) Attachments: [text/x-patch] v1-0001-Fix-use-of-openssl.path-if-openssl-isn-t-found.patch (1.1K, ../../[email protected]/2-v1-0001-Fix-use-of-openssl.path-if-openssl-isn-t-found.patch) download | inline diff: From d684d6fc1546335804d2ed82ff909991965a61a8 Mon Sep 17 00:00:00 2001 From: Tristan Partin <[email protected]> Date: Tue, 7 Nov 2023 15:59:04 -0600 Subject: [PATCH v1] Fix use of openssl.path() if openssl isn't found openssl(1) is an optional dependency of the Postgres Meson build, but was inadvertantly required when defining some SSL tests. --- src/test/ssl/meson.build | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/ssl/meson.build b/src/test/ssl/meson.build index 4cda81f3bc..c3ffcaa032 100644 --- a/src/test/ssl/meson.build +++ b/src/test/ssl/meson.build @@ -1,5 +1,10 @@ # Copyright (c) 2022-2023, PostgreSQL Global Development Group +openssl_path = '' +if openssl.found() + openssl_path = openssl.path() +endif + tests += { 'name': 'ssl', 'sd': meson.current_source_dir(), @@ -7,7 +12,7 @@ tests += { 'tap': { 'env': { 'with_ssl': ssl_library, - 'OPENSSL': openssl.path(), + 'OPENSSL': openssl_path, }, 'tests': [ 't/001_ssltests.pl', -- Tristan Partin Neon (https://neon.tech) ^ permalink raw reply [nested|flat] 75+ messages in thread
* Re: Fix use of openssl.path() if openssl isn't found 2023-11-07 22:06 Fix use of openssl.path() if openssl isn't found Tristan Partin <[email protected]> @ 2023-11-08 05:53 ` Michael Paquier <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Michael Paquier @ 2023-11-08 05:53 UTC (permalink / raw) To: Tristan Partin <[email protected]>; +Cc: pgsql-hackers On Tue, Nov 07, 2023 at 04:06:56PM -0600, Tristan Partin wrote: > Found this issue during my Fedora 39 upgrade. Tested that uninstalling > openssl still allows the various ssl tests to run and succeed. Good catch. You are right that this is inconsistent with what we expect in the test. > +openssl_path = '' > +if openssl.found() > + openssl_path = openssl.path() > +endif > + > tests += { > 'name': 'ssl', > 'sd': meson.current_source_dir(), > @@ -7,7 +12,7 @@ tests += { > 'tap': { > 'env': { > 'with_ssl': ssl_library, > - 'OPENSSL': openssl.path(), > + 'OPENSSL': openssl_path, > }, > 'tests': [ > 't/001_ssltests.pl', Okay, that's a nit and it leads to the same result, but why not using the same one-liner style like all the other meson.build files that rely on optional commands? See pg_verifybackup, pg_dump, etc. That would be more consistent. -- Michael Attachments: [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc) download ^ permalink raw reply [nested|flat] 75+ messages in thread
end of thread, other threads:[~2023-11-08 05:53 UTC | newest] Thread overview: 75+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2016-12-31 17:00 [PATCH v3] Identity columns Peter Eisentraut <[email protected]> 2019-01-30 10:26 strange error sequence on parallel btree creation Alvaro Herrera <[email protected]> 2019-01-30 17:24 ` Re: strange error sequence on parallel btree creation Peter Geoghegan <[email protected]> 2021-01-26 07:43 [PATCH v3 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-05 12:59 [PATCH v4 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-02-13 05:11 [PATCH v5 2/5] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v8 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2021-03-07 12:56 [PATCH v7 1/4] Introduce a new syntax to add/drop publications Japin Li <[email protected]> 2023-11-07 22:06 Fix use of openssl.path() if openssl isn't found Tristan Partin <[email protected]> 2023-11-08 05:53 ` Re: Fix use of openssl.path() if openssl isn't found Michael Paquier <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox