Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rxiF6-00BByZ-PO for pgsql-hackers@arkaria.postgresql.org; Fri, 19 Apr 2024 07:01:06 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1rxiF5-00GJ7v-Ei for pgsql-hackers@arkaria.postgresql.org; Fri, 19 Apr 2024 07:01:03 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rxiF3-00GJ7l-MC for pgsql-hackers@lists.postgresql.org; Fri, 19 Apr 2024 07:01:03 +0000 Received: from fhigh7-smtp.messagingengine.com ([103.168.172.158]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1rxiEt-001kuh-LF for pgsql-hackers@lists.postgresql.org; Fri, 19 Apr 2024 07:01:01 +0000 Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailfhigh.nyi.internal (Postfix) with ESMTP id 3654911400E1; Fri, 19 Apr 2024 03:00:49 -0400 (EDT) Received: from mailfrontend1 ([10.202.2.162]) by compute1.internal (MEProxy); Fri, 19 Apr 2024 03:00:49 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paquier.xyz; h= cc:cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm3; t=1713510049; x=1713596449; bh=sXzyGRt8Gb 2CfXRb28CLJPSMXMCG18EON7NK4qfhvn4=; b=AW7WxOm5pgjSGT7QlWV12SGgxl WbMuuoufxvF31Ni0IRGmSY68WvbtWyikp7nIfyXkHxZa77TcGLaullQYV+44KJUZ G5kRHh6wfJMvDyheIXFX4kucJIaU4FjcdfB1inj93mMURs8KUdUZfDpJRYMgjMYs VOsouaaGzHNLqdiXidfQTPEGeyaoS7CoTlfy5mQLPAjI/XJVghrG6NwW8dgBcGzj 6InCKg4Ax6bwEuE7Hf1GcePnmn6IUC7UFK70OCAvi3P+7cfWSTrOoAzM36SjKpOe xYHVoU4N3U5auTuY6i5zmPok8ikoouBl/v5qL2sFQq/xhovqOUde7nHo0v4Q== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm3; t=1713510049; x=1713596449; bh=sXzyGRt8Gb2CfXRb28CLJPSMXMCG 18EON7NK4qfhvn4=; b=DR8CjDkW6U4bWz+voIex/+s6Q0DCxQjkg7KJspHsL/d6 iTSTpa5vYAjXgt4fkbYIr7/kh1Ywd9QbE6zIsG+1AvrVDEtkHE+Tt67p9sI2ygvO vXwbKZC3UQiHElrpJ5Pu4AohDoaPRKgWgrTm5qAIXj2kZZmleJRBlC1SR87mryvu 72IhXmSHPlkx97QfsCno5rgIpnQ1VsGFmhnbSRjxIMFt2unPxphRIHHoxt56Xyv0 wYw59nhyKym7SuDN6YrE2CkwVrwiFGUkVGi3HJgseUJCtJN/hMFPM+tnJCsEOVtQ 4N1d9zLK1P2BsTXcanzO1K+O5uwePhK5uW+lFQE7+g== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrudekuddguddtfecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd enfghrlhcuvffnffculdejtddmnecujfgurhepfffhvfevuffkfhggtggujgesghdtreer tddtvdenucfhrhhomhepofhitghhrggvlhcurfgrqhhuihgvrhcuoehmihgthhgrvghlse hprghquhhivghrrdighiiiqeenucggtffrrghtthgvrhhnpeetleeifedufffhhfdtteel geeggeffhfekueevteeigfduudevudetgfegiedvjeenucevlhhushhtvghrufhiiigvpe dtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehmihgthhgrvghlsehprghquhhivghrrdig hiii X-ME-Proxy: Feedback-ID: i0fe9450f:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Fri, 19 Apr 2024 03:00:40 -0400 (EDT) Date: Fri, 19 Apr 2024 16:00:28 +0900 From: Michael Paquier To: Peter Eisentraut Cc: Tomas Vondra , Peter Smith , Postgres hackers Subject: Re: Sequence Access Methods, round two Message-ID: References: MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="rTEHaE/0SQFZV9PH" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --rTEHaE/0SQFZV9PH Content-Type: multipart/mixed; boundary="OKPlUpZv0l0WYI+j" Content-Disposition: inline --OKPlUpZv0l0WYI+j Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Tue, Mar 19, 2024 at 10:54:41AM +0900, Michael Paquier wrote: > I am not sure where this is leading in combination with the sequence > stuff for logical decoding, so for now I am moving this patch to the > next commit fest to discuss things in 18~. I have plans to rework this patch set for the next commit fest, and this includes some investigation about custom data types that could be plugged into these AMs. For now, please find a rebase as there were a couple of conflicts. -- Michael --OKPlUpZv0l0WYI+j Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0001-Switch-pg_sequence_last_value-to-report-a-tuple-a.patch" Content-Transfer-Encoding: quoted-printable =46rom fa5bb6f0ff4c99a9cc7408fe0dbab62f7ad16df2 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Tue, 27 Feb 2024 09:02:57 +0900 Subject: [PATCH v4 1/8] Switch pg_sequence_last_value() to report a tuple a= nd use it in pg_dump This commit switches pg_sequence_last_value() to report a tuple made of (last_value,is_called) that can be directly be used for the arguments of setval() in a sequence. Going forward with PostgreSQL 17, pg_dump and pg_sequences make use of it instead of scanning the heap table assumed to always exist for a sequence. Note: this requires a catversion bump. --- src/include/catalog/pg_proc.dat | 6 ++++-- src/backend/catalog/system_views.sql | 6 +++++- src/backend/commands/sequence.c | 19 +++++++++++++------ src/bin/pg_dump/pg_dump.c | 16 +++++++++++++--- src/test/regress/expected/rules.out | 7 ++++++- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.= dat index 134e3b22fd..1a8c8ebba5 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -3325,9 +3325,11 @@ proargmodes =3D> '{i,o,o,o,o,o,o,o}', proargnames =3D> '{sequence_oid,start_value,minimum_value,maximum_value,= increment,cycle_option,cache_size,data_type}', prosrc =3D> 'pg_sequence_parameters' }, -{ oid =3D> '4032', descr =3D> 'sequence last value', +{ oid =3D> '4032', descr =3D> 'sequence last value data', proname =3D> 'pg_sequence_last_value', provolatile =3D> 'v', proparallel= =3D> 'u', - prorettype =3D> 'int8', proargtypes =3D> 'regclass', + prorettype =3D> 'record', proargtypes =3D> 'regclass', + proallargtypes =3D> '{regclass,bool,int8}', proargmodes =3D> '{i,o,o}', + proargnames =3D> '{seqid,is_called,last_value}', prosrc =3D> 'pg_sequence_last_value' }, =20 { oid =3D> '275', descr =3D> 'return the next oid for a system table', diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/sys= tem_views.sql index 2e61f6d74e..0dfb6346ea 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -178,7 +178,11 @@ CREATE VIEW pg_sequences AS S.seqcache AS cache_size, CASE WHEN has_sequence_privilege(C.oid, 'SELECT,USAGE'::text) - THEN pg_sequence_last_value(C.oid) + THEN (SELECT + CASE WHEN sl.is_called + THEN sl.last_value ELSE NULL + END + FROM pg_sequence_last_value(C.oid) sl) ELSE NULL END AS last_value FROM pg_sequence S JOIN pg_class C ON (C.oid =3D S.seqrelid) diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequenc= e.c index 46103561c3..9646c72228 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1774,14 +1774,22 @@ pg_sequence_parameters(PG_FUNCTION_ARGS) Datum pg_sequence_last_value(PG_FUNCTION_ARGS) { +#define PG_SEQUENCE_LAST_VALUE_COLS 2 Oid relid =3D PG_GETARG_OID(0); + Datum values[PG_SEQUENCE_LAST_VALUE_COLS] =3D {0}; + bool nulls[PG_SEQUENCE_LAST_VALUE_COLS] =3D {0}; SeqTable elm; Relation seqrel; + TupleDesc tupdesc; Buffer buf; HeapTupleData seqtuple; Form_pg_sequence_data seq; bool is_called; - int64 result; + int64 last_value; + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) !=3D TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); =20 /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -1795,15 +1803,14 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) seq =3D read_seq_tuple(seqrel, &buf, &seqtuple); =20 is_called =3D seq->is_called; - result =3D seq->last_value; + last_value =3D seq->last_value; =20 UnlockReleaseBuffer(buf); sequence_close(seqrel, NoLock); =20 - if (is_called) - PG_RETURN_INT64(result); - else - PG_RETURN_NULL(); + values[0] =3D BoolGetDatum(is_called); + values[1] =3D Int64GetDatum(last_value); + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)= )); } =20 =20 diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 6d2f3fdef3..851d228f79 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -17812,9 +17812,19 @@ dumpSequenceData(Archive *fout, const TableDataInf= o *tdinfo) bool called; PQExpBuffer query =3D createPQExpBuffer(); =20 - appendPQExpBuffer(query, - "SELECT last_value, is_called FROM %s", - fmtQualifiedDumpable(tbinfo)); + /* + * In versions 17 and up, pg_sequence_last_value() has been switched to + * return a tuple with last_value and is_called. + */ + if (fout->remoteVersion >=3D 170000) + appendPQExpBuffer(query, + "SELECT last_value, is_called " + "FROM pg_sequence_last_value('%s')", + fmtQualifiedDumpable(tbinfo)); + else + appendPQExpBuffer(query, + "SELECT last_value, is_called FROM %s", + fmtQualifiedDumpable(tbinfo)); =20 res =3D ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); =20 diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expecte= d/rules.out index f4a0f36377..096ac5110b 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1700,7 +1700,12 @@ pg_sequences| SELECT n.nspname AS schemaname, s.seqcycle AS cycle, s.seqcache AS cache_size, CASE - WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN = pg_sequence_last_value((c.oid)::regclass) + WHEN has_sequence_privilege(c.oid, 'SELECT,USAGE'::text) THEN = ( SELECT + CASE + WHEN sl.is_called THEN sl.last_value + ELSE NULL::bigint + END AS "case" + FROM pg_sequence_last_value((c.oid)::regclass) sl(is_called= , last_value)) ELSE NULL::bigint END AS last_value FROM ((pg_sequence s --=20 2.43.0 --OKPlUpZv0l0WYI+j Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0002-Remove-FormData_pg_sequence_data-from-init_params.patch" Content-Transfer-Encoding: quoted-printable =46rom b87140f8842948352bdd97e9ff0cc6a28bbe0ff3 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Fri, 1 Dec 2023 12:00:45 +0900 Subject: [PATCH v4 2/8] Remove FormData_pg_sequence_data from init_params()/sequence.c init_params() sets up "last_value" and "is_called" for a sequence, based on the sequence properties in pg_sequences. This simplifies the logic around log_cnt, which is reset to 0 when the metadata of a sequence is expected to start from afresh when its properties are updated. --- src/backend/commands/sequence.c | 81 ++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequenc= e.c index 9646c72228..98a28dfb45 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -106,7 +106,9 @@ static Form_pg_sequence_data read_seq_tuple(Relation re= l, static void init_params(ParseState *pstate, List *options, bool for_identi= ty, bool isInit, Form_pg_sequence seqform, - Form_pg_sequence_data seqdataform, + int64 *last_value, + bool *reset_state, + bool *is_called, bool *need_seq_rewrite, List **owned_by); static void do_setval(Oid relid, int64 next, bool iscalled); @@ -121,7 +123,9 @@ ObjectAddress DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { FormData_pg_sequence seqform; - FormData_pg_sequence_data seqdataform; + int64 last_value; + bool reset_state; + bool is_called; bool need_seq_rewrite; List *owned_by; CreateStmt *stmt =3D makeNode(CreateStmt); @@ -164,7 +168,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) =20 /* Check and set all option values */ init_params(pstate, seq->options, seq->for_identity, true, - &seqform, &seqdataform, + &seqform, &last_value, &reset_state, &is_called, &need_seq_rewrite, &owned_by); =20 /* @@ -179,7 +183,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { case SEQ_COL_LASTVAL: coldef =3D makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] =3D Int64GetDatumFast(seqdataform.last_value); + value[i - 1] =3D Int64GetDatumFast(last_value); break; case SEQ_COL_LOG: coldef =3D makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); @@ -448,6 +452,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) ObjectAddress address; Relation rel; HeapTuple seqtuple; + bool reset_state =3D false; + bool is_called; + int64 last_value; HeapTuple newdatatuple; =20 /* Open and lock sequence, and check for ownership along the way. */ @@ -481,12 +488,14 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) /* copy the existing sequence data tuple, so it can be modified locally */ newdatatuple =3D heap_copytuple(&datatuple); newdataform =3D (Form_pg_sequence_data) GETSTRUCT(newdatatuple); + last_value =3D newdataform->last_value; + is_called =3D newdataform->is_called; =20 UnlockReleaseBuffer(buf); =20 /* Check and set new values */ init_params(pstate, stmt->options, stmt->for_identity, false, - seqform, newdataform, + seqform, &last_value, &reset_state, &is_called, &need_seq_rewrite, &owned_by); =20 /* If needed, rewrite the sequence relation itself */ @@ -513,6 +522,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) /* * Insert the modified tuple into the new storage file. */ + newdataform->last_value =3D last_value; + newdataform->is_called =3D is_called; + if (reset_state) + newdataform->log_cnt =3D 0; fill_seq_with_data(seqrel, newdatatuple); } =20 @@ -1229,17 +1242,19 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple= seqdatatuple) /* * init_params: process the options list of CREATE or ALTER SEQUENCE, and * store the values into appropriate fields of seqform, for changes that go - * into the pg_sequence catalog, and fields of seqdataform for changes to = the - * sequence relation itself. Set *need_seq_rewrite to true if we changed = any - * parameters that require rewriting the sequence's relation (interesting = for - * ALTER SEQUENCE). Also set *owned_by to any OWNED BY option, or to NIL = if - * there is none. + * into the pg_sequence catalog, and fields for changes to the sequence + * relation itself (is_called, last_value or any state it may hold). Set + * *need_seq_rewrite to true if we changed any parameters that require + * rewriting the sequence's relation (interesting for ALTER SEQUENCE). Al= so + * set *owned_by to any OWNED BY option, or to NIL if there is none. Set + * *reset_state if the internal state of the sequence needs to change on a + * follow-up nextval(). * * If isInit is true, fill any unspecified options with default values; * otherwise, do not change existing options that aren't explicitly overri= dden. * * Note: we force a sequence rewrite whenever we change parameters that af= fect - * generation of future sequence values, even if the seqdataform per se is= not + * generation of future sequence values, even if the metadata per se is not * changed. This allows ALTER SEQUENCE to behave transactionally. Curren= tly, * the only option that doesn't cause that is OWNED BY. It's *necessary* = for * ALTER SEQUENCE OWNED BY to not rewrite the sequence, because that would @@ -1250,7 +1265,9 @@ static void init_params(ParseState *pstate, List *options, bool for_identity, bool isInit, Form_pg_sequence seqform, - Form_pg_sequence_data seqdataform, + int64 *last_value, + bool *reset_state, + bool *is_called, bool *need_seq_rewrite, List **owned_by) { @@ -1353,11 +1370,11 @@ init_params(ParseState *pstate, List *options, bool= for_identity, } =20 /* - * We must reset log_cnt when isInit or when changing any parameters that - * would affect future nextval allocations. + * We must reset the state when isInit or when changing any parameters + * that would affect future nextval allocations. */ if (isInit) - seqdataform->log_cnt =3D 0; + *reset_state =3D true; =20 /* AS type */ if (as_type !=3D NULL) @@ -1406,7 +1423,7 @@ init_params(ParseState *pstate, List *options, bool f= or_identity, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("INCREMENT must not be zero"))); - seqdataform->log_cnt =3D 0; + *reset_state =3D true; } else if (isInit) { @@ -1418,7 +1435,7 @@ init_params(ParseState *pstate, List *options, bool f= or_identity, { seqform->seqcycle =3D boolVal(is_cycled->arg); Assert(BoolIsValid(seqform->seqcycle)); - seqdataform->log_cnt =3D 0; + *reset_state =3D true; } else if (isInit) { @@ -1429,7 +1446,7 @@ init_params(ParseState *pstate, List *options, bool f= or_identity, if (max_value !=3D NULL && max_value->arg) { seqform->seqmax =3D defGetInt64(max_value); - seqdataform->log_cnt =3D 0; + *reset_state =3D true; } else if (isInit || max_value !=3D NULL || reset_max_value) { @@ -1445,7 +1462,7 @@ init_params(ParseState *pstate, List *options, bool f= or_identity, } else seqform->seqmax =3D -1; /* descending seq */ - seqdataform->log_cnt =3D 0; + *reset_state =3D true; } =20 /* Validate maximum value. No need to check INT8 as seqmax is an int64 */ @@ -1461,7 +1478,7 @@ init_params(ParseState *pstate, List *options, bool f= or_identity, if (min_value !=3D NULL && min_value->arg) { seqform->seqmin =3D defGetInt64(min_value); - seqdataform->log_cnt =3D 0; + *reset_state =3D true; } else if (isInit || min_value !=3D NULL || reset_min_value) { @@ -1477,7 +1494,7 @@ init_params(ParseState *pstate, List *options, bool f= or_identity, } else seqform->seqmin =3D 1; /* ascending seq */ - seqdataform->log_cnt =3D 0; + *reset_state =3D true; } =20 /* Validate minimum value. No need to check INT8 as seqmin is an int64 */ @@ -1528,30 +1545,30 @@ init_params(ParseState *pstate, List *options, bool= for_identity, if (restart_value !=3D NULL) { if (restart_value->arg !=3D NULL) - seqdataform->last_value =3D defGetInt64(restart_value); + *last_value =3D defGetInt64(restart_value); else - seqdataform->last_value =3D seqform->seqstart; - seqdataform->is_called =3D false; - seqdataform->log_cnt =3D 0; + *last_value =3D seqform->seqstart; + *is_called =3D false; + *reset_state =3D true; } else if (isInit) { - seqdataform->last_value =3D seqform->seqstart; - seqdataform->is_called =3D false; + *last_value =3D seqform->seqstart; + *is_called =3D false; } =20 /* crosscheck RESTART (or current value, if changing MIN/MAX) */ - if (seqdataform->last_value < seqform->seqmin) + if (*last_value < seqform->seqmin) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("RESTART value (%lld) cannot be less than MINVALUE (%lld)", - (long long) seqdataform->last_value, + (long long) *last_value, (long long) seqform->seqmin))); - if (seqdataform->last_value > seqform->seqmax) + if (*last_value > seqform->seqmax) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("RESTART value (%lld) cannot be greater than MAXVALUE (%lld)", - (long long) seqdataform->last_value, + (long long) *last_value, (long long) seqform->seqmax))); =20 /* CACHE */ @@ -1563,7 +1580,7 @@ init_params(ParseState *pstate, List *options, bool f= or_identity, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("CACHE (%lld) must be greater than zero", (long long) seqform->seqcache))); - seqdataform->log_cnt =3D 0; + *reset_state =3D true; } else if (isInit) { --=20 2.43.0 --OKPlUpZv0l0WYI+j Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0003-Integrate-addition-of-attributes-for-sequences-wi.patch" Content-Transfer-Encoding: quoted-printable =46rom 506c2fb7cf6e1ae2b0f627c8e30cbde9a02297ec Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Fri, 19 Jan 2024 15:00:14 +0900 Subject: [PATCH v4 3/8] Integrate addition of attributes for sequences with ALTER TABLE This is a process similar to CREATE OR REPLACE VIEW, where attributes are added to a sequence after the initial creation of its Relation. This gives more flexibility to sequence AMs, as these may want to force their own set of attributes to use when coupled with their computation methods and/or underlying table AM. --- src/include/nodes/parsenodes.h | 1 + src/backend/commands/sequence.c | 29 +++++++++++++++++-- src/backend/commands/tablecmds.c | 10 +++++++ src/backend/tcop/utility.c | 4 +++ .../test_ddl_deparse/expected/alter_table.out | 10 +++++-- .../expected/create_sequence_1.out | 5 +++- .../expected/create_table.out | 15 ++++++++-- .../test_ddl_deparse/test_ddl_deparse.c | 3 ++ 8 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index c5f34efe27..cfe73cd737 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2353,6 +2353,7 @@ typedef struct AlterTableStmt typedef enum AlterTableType { AT_AddColumn, /* add column */ + AT_AddColumnToSequence, /* implicitly via CREATE SEQUENCE */ AT_AddColumnToView, /* implicitly via CREATE OR REPLACE VIEW */ AT_ColumnDefault, /* alter column default */ AT_CookedColumnDefault, /* add a pre-cooked column default */ diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequenc= e.c index 98a28dfb45..d6f2fc3ce0 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -136,6 +136,9 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) TupleDesc tupDesc; Datum value[SEQ_COL_LASTCOL]; bool null[SEQ_COL_LASTCOL]; + List *elts =3D NIL; + List *atcmds =3D NIL; + ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; int i; @@ -174,7 +177,6 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* * Create relation (and fill value[] and null[] for the tuple) */ - stmt->tableElts =3D NIL; for (i =3D SEQ_COL_FIRSTCOL; i <=3D SEQ_COL_LASTCOL; i++) { ColumnDef *coldef =3D NULL; @@ -198,7 +200,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_not_null =3D true; null[i - 1] =3D false; =20 - stmt->tableElts =3D lappend(stmt->tableElts, coldef); + elts =3D lappend(elts, coldef); } =20 stmt->relation =3D seq->sequence; @@ -208,12 +210,35 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->oncommit =3D ONCOMMIT_NOOP; stmt->tablespacename =3D NULL; stmt->if_not_exists =3D seq->if_not_exists; + /* + * Initial relation has no attributes, these are added later. + */ + stmt->tableElts =3D NIL; =20 address =3D DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NU= LL); seqoid =3D address.objectId; Assert(seqoid !=3D InvalidOid); =20 rel =3D sequence_open(seqoid, AccessExclusiveLock); + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd =3D makeNode(AlterTableCmd); + atcmd->subtype =3D AT_AddColumnToSequence; + atcmd->def =3D (Node *) lfirst(lc); + atcmds =3D lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + tupDesc =3D RelationGetDescr(rel); =20 /* now initialize the sequence's data */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablec= mds.c index fbffaef196..ea63412431 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4554,6 +4554,7 @@ AlterTableGetLockLevel(List *cmds) * Subcommands that may be visible to concurrent SELECTs */ case AT_DropColumn: /* change visible to SELECT */ + case AT_AddColumnToSequence: /* CREATE SEQUENCE */ case AT_AddColumnToView: /* CREATE VIEW */ case AT_DropOids: /* used to equiv to DropColumn */ case AT_EnableAlwaysRule: /* may change SELECT rules */ @@ -4857,6 +4858,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd= *cmd, /* Recursion occurs during execution phase */ pass =3D AT_PASS_ADD_COL; break; + case AT_AddColumnToSequence: /* add column via CREATE SEQUENCE */ + ATSimplePermissions(cmd->subtype, rel, ATT_SEQUENCE); + ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd, + lockmode, context); + /* Recursion occurs during execution phase */ + pass =3D AT_PASS_ADD_COL; + break; case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ ATSimplePermissions(cmd->subtype, rel, ATT_VIEW); ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd, @@ -5284,6 +5292,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, switch (cmd->subtype) { case AT_AddColumn: /* ADD COLUMN */ + case AT_AddColumnToSequence: /* add column via CREATE SEQUENCE */ case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ address =3D ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, @@ -6464,6 +6473,7 @@ alter_table_type_to_string(AlterTableType cmdtype) switch (cmdtype) { case AT_AddColumn: + case AT_AddColumnToSequence: case AT_AddColumnToView: return "ADD COLUMN"; case AT_ColumnDefault: diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index fa66b8017e..b64acfb23f 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1664,7 +1664,11 @@ ProcessUtilitySlow(ParseState *pstate, break; =20 case T_CreateSeqStmt: + EventTriggerAlterTableStart(parsetree); address =3D DefineSequence(pstate, (CreateSeqStmt *) parsetree); + /* stashed internally */ + commandCollected =3D true; + EventTriggerAlterTableEnd(); break; =20 case T_AlterSeqStmt: diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/s= rc/test/modules/test_ddl_deparse/expected/alter_table.out index b5e71af9aa..7ebd85200f 100644 --- a/src/test/modules/test_ddl_deparse/expected/alter_table.out +++ b/src/test/modules/test_ddl_deparse/expected/alter_table.out @@ -25,7 +25,10 @@ NOTICE: DDL test: type simple, tag CREATE TABLE CREATE TABLE grandchild () INHERITS (child); NOTICE: DDL test: type simple, tag CREATE TABLE ALTER TABLE parent ADD COLUMN b serial; -NOTICE: DDL test: type simple, tag CREATE SEQUENCE +NOTICE: DDL test: type alter table, tag CREATE SEQUENCE +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column last_value = of sequence parent_b_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of = sequence parent_b_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column is_called o= f sequence parent_b_seq NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of tabl= e parent NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint p= arent_b_not_null on table parent @@ -71,7 +74,10 @@ ALTER TABLE parent ALTER COLUMN a SET NOT NULL; NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type SET NOT NULL (and recurse) desc constraint par= ent_a_not_null on table parent ALTER TABLE parent ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -NOTICE: DDL test: type simple, tag CREATE SEQUENCE +NOTICE: DDL test: type alter table, tag CREATE SEQUENCE +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column last_value = of sequence parent_a_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of = sequence parent_a_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column is_called o= f sequence parent_a_seq NOTICE: DDL test: type simple, tag ALTER SEQUENCE NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type ADD IDENTITY (and recurse) desc column a of ta= ble parent diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.o= ut b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out index 5837ea484e..310ce5a6ba 100644 --- a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out +++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out @@ -8,4 +8,7 @@ CREATE SEQUENCE fkey_table_seq START 10 CACHE 10 CYCLE; -NOTICE: DDL test: type simple, tag CREATE SEQUENCE +NOTICE: DDL test: type alter table, tag CREATE SEQUENCE +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column last_value = of sequence fkey_table_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of = sequence fkey_table_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column is_called o= f sequence fkey_table_seq diff --git a/src/test/modules/test_ddl_deparse/expected/create_table.out b/= src/test/modules/test_ddl_deparse/expected/create_table.out index 75b62aff4d..69e54358ee 100644 --- a/src/test/modules/test_ddl_deparse/expected/create_table.out +++ b/src/test/modules/test_ddl_deparse/expected/create_table.out @@ -50,9 +50,18 @@ CREATE TABLE datatype_table ( PRIMARY KEY (id), UNIQUE (id_big) ); -NOTICE: DDL test: type simple, tag CREATE SEQUENCE -NOTICE: DDL test: type simple, tag CREATE SEQUENCE -NOTICE: DDL test: type simple, tag CREATE SEQUENCE +NOTICE: DDL test: type alter table, tag CREATE SEQUENCE +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column last_value = of sequence datatype_table_id_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of = sequence datatype_table_id_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column is_called o= f sequence datatype_table_id_seq +NOTICE: DDL test: type alter table, tag CREATE SEQUENCE +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column last_value = of sequence datatype_table_id_big_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of = sequence datatype_table_id_big_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column is_called o= f sequence datatype_table_id_big_seq +NOTICE: DDL test: type alter table, tag CREATE SEQUENCE +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column last_value = of sequence datatype_table_is_small_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column log_cnt of = sequence datatype_table_is_small_seq +NOTICE: subcommand: type ADD COLUMN TO SEQUENCE desc column is_called o= f sequence datatype_table_is_small_seq NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type SET ATTNOTNULL desc diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/tes= t/modules/test_ddl_deparse/test_ddl_deparse.c index 265ef2a547..4be07bcbc8 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -114,6 +114,9 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS) case AT_AddColumn: strtype =3D "ADD COLUMN"; break; + case AT_AddColumnToSequence: + strtype =3D "ADD COLUMN TO SEQUENCE"; + break; case AT_AddColumnToView: strtype =3D "ADD COLUMN TO VIEW"; break; --=20 2.43.0 --OKPlUpZv0l0WYI+j Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0004-Move-code-for-local-sequences-to-own-file.patch" Content-Transfer-Encoding: quoted-printable =46rom 113c32eef9eef84599c0b9c9909791a4814b62b3 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Fri, 19 Jan 2024 15:01:55 +0900 Subject: [PATCH v4 4/8] Move code for local sequences to own file Now that the separation between the in-core sequence computations and the catalog layer is clean, this moves the code corresponding to the "local" sequence AM into its own file, out of sequence.c. The WAL routines related to sequence are moved in it as well. --- src/include/access/localam.h | 48 ++ src/include/access/rmgrlist.h | 2 +- src/backend/access/rmgrdesc/Makefile | 2 +- .../rmgrdesc/{seqdesc.c =3D> localseqdesc.c} | 18 +- src/backend/access/rmgrdesc/meson.build | 2 +- src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/local.c | 706 ++++++++++++++++++ src/backend/access/sequence/meson.build | 1 + src/backend/access/transam/rmgr.c | 1 + src/backend/commands/sequence.c | 619 +-------------- src/bin/pg_waldump/.gitignore | 2 +- src/bin/pg_waldump/rmgrdesc.c | 1 + 12 files changed, 793 insertions(+), 611 deletions(-) create mode 100644 src/include/access/localam.h rename src/backend/access/rmgrdesc/{seqdesc.c =3D> localseqdesc.c} (69%) create mode 100644 src/backend/access/sequence/local.c diff --git a/src/include/access/localam.h b/src/include/access/localam.h new file mode 100644 index 0000000000..5b0575dc2e --- /dev/null +++ b/src/include/access/localam.h @@ -0,0 +1,48 @@ +/*------------------------------------------------------------------------- + * + * localam.h + * Local sequence access method. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/localam.h + * + *------------------------------------------------------------------------- + */ +#ifndef LOCALAM_H +#define LOCALAM_H + +#include "access/xlogreader.h" +#include "storage/relfilelocator.h" +#include "utils/rel.h" + +/* XLOG stuff */ +#define XLOG_LOCAL_SEQ_LOG 0x00 + +typedef struct xl_local_seq_rec +{ + RelFileLocator locator; + /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ +} xl_local_seq_rec; + +extern void local_seq_redo(XLogReaderState *record); +extern void local_seq_desc(StringInfo buf, XLogReaderState *record); +extern const char *local_seq_identify(uint8 info); +extern void local_seq_mask(char *page, BlockNumber blkno); + +/* access routines */ +extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); +extern const char *local_seq_get_table_am(void); +extern void local_seq_init(Relation rel, int64 last_value, bool is_called); +extern void local_seq_setval(Relation rel, int64 next, bool iscalled); +extern void local_seq_reset(Relation rel, int64 startv, bool is_called, + bool reset_state); +extern void local_seq_get_state(Relation rel, int64 *last_value, + bool *is_called); +extern void local_seq_change_persistence(Relation rel, + char newrelpersistence); + +#endif /* LOCALAM_H */ diff --git a/src/include/access/rmgrlist.h b/src/include/access/rmgrlist.h index 78e6b908c6..46fd63ae47 100644 --- a/src/include/access/rmgrlist.h +++ b/src/include/access/rmgrlist.h @@ -40,7 +40,7 @@ PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, btr= ee_identify, btree_xlog PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NUL= L, hash_mask, NULL) PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_start= up, gin_xlog_cleanup, gin_mask, NULL) PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_identify, gist_xlog= _startup, gist_xlog_cleanup, gist_mask, NULL) -PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, seq_identify, NULL, NUL= L, seq_mask, NULL) +PG_RMGR(RM_LOCAL_SEQ_ID, "LocalSequence", local_seq_redo, local_seq_desc, = local_seq_identify, NULL, NULL, local_seq_mask, NULL) PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_identify, spg_xlog= _startup, spg_xlog_cleanup, spg_mask, NULL) PG_RMGR(RM_BRIN_ID, "BRIN", brin_redo, brin_desc, brin_identify, NULL, NUL= L, brin_mask, NULL) PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commi= t_ts_identify, NULL, NULL, NULL, NULL) diff --git a/src/backend/access/rmgrdesc/Makefile b/src/backend/access/rmgr= desc/Makefile index cd95eec37f..dff5a60e68 100644 --- a/src/backend/access/rmgrdesc/Makefile +++ b/src/backend/access/rmgrdesc/Makefile @@ -18,13 +18,13 @@ OBJS =3D \ gistdesc.o \ hashdesc.o \ heapdesc.o \ + localseqdesc.o \ logicalmsgdesc.o \ mxactdesc.o \ nbtdesc.o \ relmapdesc.o \ replorigindesc.o \ rmgrdesc_utils.o \ - seqdesc.o \ smgrdesc.o \ spgdesc.o \ standbydesc.o \ diff --git a/src/backend/access/rmgrdesc/seqdesc.c b/src/backend/access/rmg= rdesc/localseqdesc.c similarity index 69% rename from src/backend/access/rmgrdesc/seqdesc.c rename to src/backend/access/rmgrdesc/localseqdesc.c index cf0e02ded5..17b8b71093 100644 --- a/src/backend/access/rmgrdesc/seqdesc.c +++ b/src/backend/access/rmgrdesc/localseqdesc.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * - * seqdesc.c - * rmgr descriptor routines for commands/sequence.c + * localseqdesc.c + * rmgr descriptor routines for sequence/local.c * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -14,31 +14,31 @@ */ #include "postgres.h" =20 -#include "commands/sequence.h" +#include "access/localam.h" =20 =20 void -seq_desc(StringInfo buf, XLogReaderState *record) +local_seq_desc(StringInfo buf, XLogReaderState *record) { char *rec =3D XLogRecGetData(record); uint8 info =3D XLogRecGetInfo(record) & ~XLR_INFO_MASK; - xl_seq_rec *xlrec =3D (xl_seq_rec *) rec; + xl_local_seq_rec *xlrec =3D (xl_local_seq_rec *) rec; =20 - if (info =3D=3D XLOG_SEQ_LOG) + if (info =3D=3D XLOG_LOCAL_SEQ_LOG) appendStringInfo(buf, "rel %u/%u/%u", xlrec->locator.spcOid, xlrec->locator.dbOid, xlrec->locator.relNumber); } =20 const char * -seq_identify(uint8 info) +local_seq_identify(uint8 info) { const char *id =3D NULL; =20 switch (info & ~XLR_INFO_MASK) { - case XLOG_SEQ_LOG: - id =3D "LOG"; + case XLOG_LOCAL_SEQ_LOG: + id =3D "LOCAL_SEQ_LOG"; break; } =20 diff --git a/src/backend/access/rmgrdesc/meson.build b/src/backend/access/r= mgrdesc/meson.build index e8b7a65fc7..5f2f28072b 100644 --- a/src/backend/access/rmgrdesc/meson.build +++ b/src/backend/access/rmgrdesc/meson.build @@ -11,13 +11,13 @@ rmgr_desc_sources =3D files( 'gistdesc.c', 'hashdesc.c', 'heapdesc.c', + 'localseqdesc.c', 'logicalmsgdesc.c', 'mxactdesc.c', 'nbtdesc.c', 'relmapdesc.c', 'replorigindesc.c', 'rmgrdesc_utils.c', - 'seqdesc.c', 'smgrdesc.c', 'spgdesc.c', 'standbydesc.c', diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequ= ence/Makefile index 9f9d31f542..697f89905e 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir =3D src/backend/access/sequence top_builddir =3D ../../../.. include $(top_builddir)/src/Makefile.global =20 -OBJS =3D sequence.o +OBJS =3D local.o sequence.o =20 include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/local.c b/src/backend/access/seque= nce/local.c new file mode 100644 index 0000000000..ac124095fb --- /dev/null +++ b/src/backend/access/sequence/local.c @@ -0,0 +1,706 @@ +/*------------------------------------------------------------------------- + * + * local.c + * Local sequence access manager + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/local.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/bufmask.h" +#include "access/localam.h" +#include "access/multixact.h" +#include "access/xact.h" +#include "access/xloginsert.h" +#include "access/xlogutils.h" +#include "catalog/storage_xlog.h" +#include "commands/tablecmds.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" + + +/* + * We don't want to log each fetching of a value from a sequence, + * so we pre-log a few fetches in advance. In the event of + * crash we can lose (skip over) as many values as we pre-logged. + */ +#define LOCAL_SEQ_LOG_VALS 32 + +/* + * The "special area" of a local sequence's buffer page looks like this. + */ +#define LOCAL_SEQ_MAGIC 0x1717 + +typedef struct local_sequence_magic +{ + uint32 magic; +} local_sequence_magic; + +/* Format of tuples stored in heap table associated to local sequences */ +typedef struct FormData_pg_sequence_data +{ + int64 last_value; + int64 log_cnt; + bool is_called; +} FormData_pg_sequence_data; + +typedef FormData_pg_sequence_data *Form_pg_sequence_data; + +/* + * Columns of a local sequence relation + */ +#define SEQ_COL_LASTVAL 1 +#define SEQ_COL_LOG 2 +#define SEQ_COL_CALLED 3 + +#define SEQ_COL_FIRSTCOL SEQ_COL_LASTVAL +#define SEQ_COL_LASTCOL SEQ_COL_CALLED + + +/* + * We don't want to log each fetching of a value from a sequence, + * so we pre-log a few fetches in advance. In the event of + * crash we can lose (skip over) as many values as we pre-logged. + */ +#define SEQ_LOG_VALS 32 + +static Form_pg_sequence_data read_seq_tuple(Relation rel, + Buffer *buf, + HeapTuple seqdatatuple); +static void fill_seq_with_data(Relation rel, HeapTuple tuple); +static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, + ForkNumber forkNum); + +/* + * Given an opened sequence relation, lock the page buffer and find the tu= ple + * + * *buf receives the reference to the pinned-and-ex-locked buffer + * *seqdatatuple receives the reference to the sequence tuple proper + * (this arg should point to a local variable of type HeapTupleData) + * + * Function's return value points to the data payload of the tuple + */ +static Form_pg_sequence_data +read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) +{ + Page page; + ItemId lp; + local_sequence_magic *sm; + Form_pg_sequence_data seq; + + *buf =3D ReadBuffer(rel, 0); + LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); + + page =3D BufferGetPage(*buf); + sm =3D (local_sequence_magic *) PageGetSpecialPointer(page); + + if (sm->magic !=3D LOCAL_SEQ_MAGIC) + elog(ERROR, "bad magic number in sequence \"%s\": %08X", + RelationGetRelationName(rel), sm->magic); + + lp =3D PageGetItemId(page, FirstOffsetNumber); + Assert(ItemIdIsNormal(lp)); + + /* Note we currently only bother to set these two fields of *seqdatatuple= */ + seqdatatuple->t_data =3D (HeapTupleHeader) PageGetItem(page, lp); + seqdatatuple->t_len =3D ItemIdGetLength(lp); + + /* + * Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on + * a sequence, which would leave a non-frozen XID in the sequence tuple's + * xmax, which eventually leads to clog access failures or worse. If we + * see this has happened, clean up after it. We treat this like a hint + * bit update, ie, don't bother to WAL-log it, since we can certainly do + * this again if the update gets lost. + */ + Assert(!(seqdatatuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)); + if (HeapTupleHeaderGetRawXmax(seqdatatuple->t_data) !=3D InvalidTransacti= onId) + { + HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); + seqdatatuple->t_data->t_infomask &=3D ~HEAP_XMAX_COMMITTED; + seqdatatuple->t_data->t_infomask |=3D HEAP_XMAX_INVALID; + MarkBufferDirtyHint(*buf, true); + } + + seq =3D (Form_pg_sequence_data) GETSTRUCT(seqdatatuple); + + return seq; +} + +/* + * Initialize a sequence's relation with the specified tuple as content + * + * This handles unlogged sequences by writing to both the main and the init + * fork as necessary. + */ +static void +fill_seq_with_data(Relation rel, HeapTuple tuple) +{ + fill_seq_fork_with_data(rel, tuple, MAIN_FORKNUM); + + if (rel->rd_rel->relpersistence =3D=3D RELPERSISTENCE_UNLOGGED) + { + SMgrRelation srel; + + srel =3D smgropen(rel->rd_locator, INVALID_PROC_NUMBER); + smgrcreate(srel, INIT_FORKNUM, false); + log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); + fill_seq_fork_with_data(rel, tuple, INIT_FORKNUM); + FlushRelationBuffers(rel); + smgrclose(srel); + } +} + +/* + * Initialize a sequence's relation fork with the specified tuple as conte= nt + */ +static void +fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) +{ + Buffer buf; + Page page; + local_sequence_magic *sm; + OffsetNumber offnum; + + /* Initialize first page of relation with special magic number */ + + buf =3D ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, + EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); + Assert(BufferGetBlockNumber(buf) =3D=3D 0); + + page =3D BufferGetPage(buf); + + PageInit(page, BufferGetPageSize(buf), sizeof(local_sequence_magic)); + sm =3D (local_sequence_magic *) PageGetSpecialPointer(page); + sm->magic =3D LOCAL_SEQ_MAGIC; + + /* Now insert sequence tuple */ + + /* + * Since VACUUM does not process sequences, we have to force the tuple to + * have xmin =3D FrozenTransactionId now. Otherwise it would become + * invisible to SELECTs after 2G transactions. It is okay to do this + * because if the current transaction aborts, no other xact will ever + * examine the sequence tuple anyway. + */ + HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId); + HeapTupleHeaderSetXminFrozen(tuple->t_data); + HeapTupleHeaderSetCmin(tuple->t_data, FirstCommandId); + HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId); + tuple->t_data->t_infomask |=3D HEAP_XMAX_INVALID; + ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); + + /* check the comment above nextval_internal()'s equivalent call. */ + if (RelationNeedsWAL(rel)) + GetTopTransactionId(); + + START_CRIT_SECTION(); + + MarkBufferDirty(buf); + + offnum =3D PageAddItem(page, (Item) tuple->t_data, tuple->t_len, + InvalidOffsetNumber, false, false); + if (offnum !=3D FirstOffsetNumber) + elog(ERROR, "failed to add sequence tuple to page"); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel) || forkNum =3D=3D INIT_FORKNUM) + { + xl_local_seq_rec xlrec; + XLogRecPtr recptr; + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator =3D rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec)); + XLogRegisterData((char *) tuple->t_data, tuple->t_len); + + recptr =3D XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * Mask a Sequence page before performing consistency checks on it. + */ +void +local_seq_mask(char *page, BlockNumber blkno) +{ + mask_page_lsn_and_checksum(page); + + mask_unused_space(page); +} + +void +local_seq_redo(XLogReaderState *record) +{ + XLogRecPtr lsn =3D record->EndRecPtr; + uint8 info =3D XLogRecGetInfo(record) & ~XLR_INFO_MASK; + Buffer buffer; + Page page; + Page localpage; + char *item; + Size itemsz; + xl_local_seq_rec *xlrec =3D (xl_local_seq_rec *) XLogRecGetData(record); + local_sequence_magic *sm; + + if (info !=3D XLOG_LOCAL_SEQ_LOG) + elog(PANIC, "seq_redo: unknown op code %u", info); + + buffer =3D XLogInitBufferForRedo(record, 0); + page =3D (Page) BufferGetPage(buffer); + + /* + * We always reinit the page. However, since this WAL record type is also + * used for updating sequences, it's possible that a hot-standby backend + * is examining the page concurrently; so we mustn't transiently trash the + * buffer. The solution is to build the correct new page contents in + * local workspace and then memcpy into the buffer. Then only bytes that + * are supposed to change will change, even transiently. We must palloc + * the local page for alignment reasons. + */ + localpage =3D (Page) palloc(BufferGetPageSize(buffer)); + + PageInit(localpage, BufferGetPageSize(buffer), sizeof(local_sequence_magi= c)); + sm =3D (local_sequence_magic *) PageGetSpecialPointer(localpage); + sm->magic =3D LOCAL_SEQ_MAGIC; + + item =3D (char *) xlrec + sizeof(xl_local_seq_rec); + itemsz =3D XLogRecGetDataLen(record) - sizeof(xl_local_seq_rec); + + if (PageAddItem(localpage, (Item) item, itemsz, + FirstOffsetNumber, false, false) =3D=3D InvalidOffsetNumber) + elog(PANIC, "local_seq_redo: failed to add item to page"); + + PageSetLSN(localpage, lsn); + + memcpy(page, localpage, BufferGetPageSize(buffer)); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); + + pfree(localpage); +} + +/* + * local_seq_nextval() + * + * Allocate a new value for a local sequence, based on the sequence + * configuration. + */ +int64 +local_seq_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + int64 result; + int64 fetch; + int64 next; + int64 rescnt =3D 0; + int64 log; + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_sequence_data seq; + Page page; + bool logit =3D false; + + /* lock page buffer and read tuple */ + seq =3D read_seq_tuple(rel, &buf, &seqdatatuple); + page =3D BufferGetPage(buf); + + *last =3D next =3D result =3D seq->last_value; + fetch =3D cache; + log =3D seq->log_cnt; + + if (!seq->is_called) + { + rescnt++; /* return last_value if not is_called */ + fetch--; + } + + /* + * Decide whether we should emit a WAL log record. If so, force up the + * fetch count to grab SEQ_LOG_VALS more values than we actually need to + * cache. (These will then be usable without logging.) + * + * If this is the first nextval after a checkpoint, we must force a new + * WAL record to be written anyway, else replay starting from the + * checkpoint would fail to advance the sequence past the logged values. + * In this case we may as well fetch extra values. + */ + if (log < fetch || !seq->is_called) + { + /* forced log to satisfy local demand for values */ + fetch =3D log =3D fetch + SEQ_LOG_VALS; + logit =3D true; + } + else + { + XLogRecPtr redoptr =3D GetRedoRecPtr(); + + if (PageGetLSN(page) <=3D redoptr) + { + /* last update of seq was before checkpoint */ + fetch =3D log =3D fetch + SEQ_LOG_VALS; + logit =3D true; + } + } + + while (fetch) /* try to fetch cache [+ log ] numbers */ + { + /* + * Check MAXVALUE for ascending sequences and MINVALUE for descending + * sequences + */ + if (incby > 0) + { + /* ascending sequence */ + if ((maxv >=3D 0 && next > maxv - incby) || + (maxv < 0 && next + incby > maxv)) + { + if (rescnt > 0) + break; /* stop fetching */ + if (!cycle) + ereport(ERROR, + (errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED), + errmsg("nextval: reached maximum value of sequence \"%s\" (%lld)", + RelationGetRelationName(rel), + (long long) maxv))); + next =3D minv; + } + else + next +=3D incby; + } + else + { + /* descending sequence */ + if ((minv < 0 && next < minv - incby) || + (minv >=3D 0 && next + incby < minv)) + { + if (rescnt > 0) + break; /* stop fetching */ + if (!cycle) + ereport(ERROR, + (errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED), + errmsg("nextval: reached minimum value of sequence \"%s\" (%lld)", + RelationGetRelationName(rel), + (long long) minv))); + next =3D maxv; + } + else + next +=3D incby; + } + fetch--; + if (rescnt < cache) + { + log--; + rescnt++; + *last =3D next; + if (rescnt =3D=3D 1) /* if it's first result - */ + result =3D next; /* it's what to return */ + } + } + + log -=3D fetch; /* adjust for any unfetched numbers */ + Assert(log >=3D 0); + + /* + * If something needs to be WAL logged, acquire an xid, so this + * transaction's commit will trigger a WAL flush and wait for syncrep. + * It's sufficient to ensure the toplevel transaction has an xid, no need + * to assign xids subxacts, that'll already trigger an appropriate wait. + * (Have to do that here, so we're outside the critical section) + */ + if (logit && RelationNeedsWAL(rel)) + GetTopTransactionId(); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + + /* + * We must mark the buffer dirty before doing XLogInsert(); see notes in + * SyncOneBuffer(). However, we don't apply the desired changes just yet. + * This looks like a violation of the buffer update protocol, but it is in + * fact safe because we hold exclusive lock on the buffer. Any other + * process, including a checkpoint, that tries to examine the buffer + * contents will block until we release the lock, and then will see the + * final state that we install below. + */ + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (logit && RelationNeedsWAL(rel)) + { + xl_local_seq_rec xlrec; + XLogRecPtr recptr; + + /* + * We don't log the current state of the tuple, but rather the state + * as it would appear after "log" more fetches. This lets us skip + * that many future WAL records, at the cost that we lose those + * sequence values if we crash. + */ + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + /* set values that will be saved in xlog */ + seq->last_value =3D next; + seq->is_called =3D true; + seq->log_cnt =3D 0; + + xlrec.locator =3D rel->rd_locator; + + XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr =3D XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG); + + PageSetLSN(page, recptr); + } + + /* Now update sequence tuple to the intended final state */ + seq->last_value =3D *last; /* last fetched number */ + seq->is_called =3D true; + seq->log_cnt =3D log; /* how much is logged */ + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + return result; +} + +/* + * local_seq_get_table_am() + * + * Return the table access method used by this sequence. + */ +const char * +local_seq_get_table_am(void) +{ + return "heap"; +} + +/* + * local_seq_init() + * + * Add the sequence attributes to the relation created for this sequence + * AM and insert a tuple of metadata into the sequence relation, based on + * the information guessed from pg_sequences. This is the first tuple + * inserted after the relation has been created, filling in its heap + * table. + */ +void +local_seq_init(Relation rel, int64 last_value, bool is_called) +{ + Datum value[SEQ_COL_LASTCOL]; + bool null[SEQ_COL_LASTCOL]; + List *elts =3D NIL; + List *atcmds =3D NIL; + ListCell *lc; + TupleDesc tupdesc; + HeapTuple tuple; + + /* + * Create relation (and fill value[] and null[] for the initial tuple). + */ + for (int i =3D SEQ_COL_FIRSTCOL; i <=3D SEQ_COL_LASTCOL; i++) + { + ColumnDef *coldef =3D NULL; + + switch (i) + { + case SEQ_COL_LASTVAL: + coldef =3D makeColumnDef("last_value", INT8OID, -1, InvalidOid); + value[i - 1] =3D Int64GetDatumFast(last_value); + break; + case SEQ_COL_LOG: + coldef =3D makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); + value[i - 1] =3D Int64GetDatum(0); + break; + case SEQ_COL_CALLED: + coldef =3D makeColumnDef("is_called", BOOLOID, -1, InvalidOid); + value[i - 1] =3D BoolGetDatum(is_called); + break; + } + + coldef->is_not_null =3D true; + null[i - 1] =3D false; + elts =3D lappend(elts, coldef); + } + + /* Add all the attributes to the sequence */ + foreach(lc, elts) + { + AlterTableCmd *atcmd; + + atcmd =3D makeNode(AlterTableCmd); + atcmd->subtype =3D AT_AddColumnToSequence; + atcmd->def =3D (Node *) lfirst(lc); + atcmds =3D lappend(atcmds, atcmd); + } + + /* + * No recursion needed. Note that EventTriggerAlterTableStart() should + * have been called. + */ + AlterTableInternal(RelationGetRelid(rel), atcmds, false); + CommandCounterIncrement(); + + tupdesc =3D RelationGetDescr(rel); + tuple =3D heap_form_tuple(tupdesc, value, null); + fill_seq_with_data(rel, tuple); +} + +/* + * local_seq_setval() + * + * Callback for setval(). + */ +void +local_seq_setval(Relation rel, int64 next, bool iscalled) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_sequence_data seq; + + /* lock page buffer and read tuple */ + seq =3D read_seq_tuple(rel, &buf, &seqdatatuple); + + /* ready to change the on-disk (or really, in-buffer) tuple */ + START_CRIT_SECTION(); + seq->last_value =3D next; /* last fetched number */ + seq->is_called =3D iscalled; + seq->log_cnt =3D 0; + + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + { + xl_local_seq_rec xlrec; + XLogRecPtr recptr; + Page page =3D BufferGetPage(buf); + + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); + + xlrec.locator =3D rel->rd_locator; + XLogRegisterData((char *) &xlrec, sizeof(xl_local_seq_rec)); + XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); + + recptr =3D XLogInsert(RM_LOCAL_SEQ_ID, XLOG_LOCAL_SEQ_LOG); + + PageSetLSN(page, recptr); + } + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); +} + +/* + * local_seq_reset() + * + * Perform a hard reset on the local sequence, rewriting its heap data + * entirely. + */ +void +local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_sta= te) +{ + Form_pg_sequence_data seq; + Buffer buf; + HeapTupleData seqdatatuple; + HeapTuple tuple; + + /* lock buffer page and read tuple */ + (void) read_seq_tuple(rel, &buf, &seqdatatuple); + + /* + * Copy the existing sequence tuple. + */ + tuple =3D heap_copytuple(&seqdatatuple); + + /* Now we're done with the old page */ + UnlockReleaseBuffer(buf); + + /* + * Modify the copied tuple to execute the restart (compare the RESTART + * action in AlterSequence) + */ + seq =3D (Form_pg_sequence_data) GETSTRUCT(tuple); + seq->last_value =3D startv; + seq->is_called =3D is_called; + if (reset_state) + seq->log_cnt =3D 0; + + /* + * Create a new storage file for the sequence. + */ + RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence); + + /* + * Ensure sequence's relfrozenxid is at 0, since it won't contain any + * unfrozen XIDs. Same with relminmxid, since a sequence will never + * contain multixacts. + */ + Assert(rel->rd_rel->relfrozenxid =3D=3D InvalidTransactionId); + Assert(rel->rd_rel->relminmxid =3D=3D InvalidMultiXactId); + + /* + * Insert the modified tuple into the new storage file. + */ + fill_seq_with_data(rel, tuple); +} + +/* + * local_seq_get_state() + * + * Retrieve the state of a local sequence. + */ +void +local_seq_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + Buffer buf; + HeapTupleData seqdatatuple; + Form_pg_sequence_data seq; + + /* lock page buffer and read tuple */ + seq =3D read_seq_tuple(rel, &buf, &seqdatatuple); + + *last_value =3D seq->last_value; + *is_called =3D seq->is_called; + + UnlockReleaseBuffer(buf); +} + +/* + * local_seq_change_persistence() + * + * Persistence change for the local sequence Relation. + */ +void +local_seq_change_persistence(Relation rel, char newrelpersistence) +{ + Buffer buf; + HeapTupleData seqdatatuple; + + (void) read_seq_tuple(rel, &buf, &seqdatatuple); + RelationSetNewRelfilenumber(rel, newrelpersistence); + fill_seq_with_data(rel, &seqdatatuple); + UnlockReleaseBuffer(buf); +} diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/s= equence/meson.build index 1843aed38d..8c4ef42467 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -1,5 +1,6 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group =20 backend_sources +=3D files( + 'local.c', 'sequence.c', ) diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam= /rmgr.c index 3e2f1d4a23..1ec20459cb 100644 --- a/src/backend/access/transam/rmgr.c +++ b/src/backend/access/transam/rmgr.c @@ -27,6 +27,7 @@ #include "access/gistxlog.h" #include "access/hash_xlog.h" #include "access/heapam_xlog.h" +#include "access/localam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/spgxlog.h" diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequenc= e.c index d6f2fc3ce0..d7a6523292 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,6 +16,7 @@ =20 #include "access/bufmask.h" #include "access/htup_details.h" +#include "access/localam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" @@ -50,23 +51,6 @@ #include "utils/varlena.h" =20 =20 -/* - * We don't want to log each fetching of a value from a sequence, - * so we pre-log a few fetches in advance. In the event of - * crash we can lose (skip over) as many values as we pre-logged. - */ -#define SEQ_LOG_VALS 32 - -/* - * The "special area" of a sequence's buffer page looks like this. - */ -#define SEQ_MAGIC 0x1717 - -typedef struct sequence_magic -{ - uint32 magic; -} sequence_magic; - /* * We store a SeqTable item for every sequence we have touched in the curr= ent * session. This is needed to hold onto nextval/currval state. (We can't @@ -96,13 +80,9 @@ static HTAB *seqhashtab =3D NULL; /* hash table for SeqT= able items */ */ static SeqTableData *last_used_seq =3D NULL; =20 -static void fill_seq_with_data(Relation rel, HeapTuple tuple); -static void fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNum= ber forkNum); static Relation lock_and_open_sequence(SeqTable seq); static void create_seq_hashtable(void); static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel); -static Form_pg_sequence_data read_seq_tuple(Relation rel, - Buffer *buf, HeapTuple seqdatatuple); static void init_params(ParseState *pstate, List *options, bool for_identi= ty, bool isInit, Form_pg_sequence seqform, @@ -134,14 +114,8 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) Relation rel; HeapTuple tuple; TupleDesc tupDesc; - Datum value[SEQ_COL_LASTCOL]; - bool null[SEQ_COL_LASTCOL]; - List *elts =3D NIL; - List *atcmds =3D NIL; - ListCell *lc; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; - int i; =20 /* * If if_not_exists was given and a relation with the same name already @@ -174,35 +148,6 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) &seqform, &last_value, &reset_state, &is_called, &need_seq_rewrite, &owned_by); =20 - /* - * Create relation (and fill value[] and null[] for the tuple) - */ - for (i =3D SEQ_COL_FIRSTCOL; i <=3D SEQ_COL_LASTCOL; i++) - { - ColumnDef *coldef =3D NULL; - - switch (i) - { - case SEQ_COL_LASTVAL: - coldef =3D makeColumnDef("last_value", INT8OID, -1, InvalidOid); - value[i - 1] =3D Int64GetDatumFast(last_value); - break; - case SEQ_COL_LOG: - coldef =3D makeColumnDef("log_cnt", INT8OID, -1, InvalidOid); - value[i - 1] =3D Int64GetDatum((int64) 0); - break; - case SEQ_COL_CALLED: - coldef =3D makeColumnDef("is_called", BOOLOID, -1, InvalidOid); - value[i - 1] =3D BoolGetDatum(false); - break; - } - - coldef->is_not_null =3D true; - null[i - 1] =3D false; - - elts =3D lappend(elts, coldef); - } - stmt->relation =3D seq->sequence; stmt->inhRelations =3D NIL; stmt->constraints =3D NIL; @@ -215,35 +160,20 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) */ stmt->tableElts =3D NIL; =20 + /* + * Initial relation has no attributes, these can be added later via the + * "init" AM callback. + */ + stmt->tableElts =3D NIL; + address =3D DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NU= LL); seqoid =3D address.objectId; Assert(seqoid !=3D InvalidOid); =20 rel =3D sequence_open(seqoid, AccessExclusiveLock); =20 - /* Add all the attributes to the sequence */ - foreach(lc, elts) - { - AlterTableCmd *atcmd; - - atcmd =3D makeNode(AlterTableCmd); - atcmd->subtype =3D AT_AddColumnToSequence; - atcmd->def =3D (Node *) lfirst(lc); - atcmds =3D lappend(atcmds, atcmd); - } - - /* - * No recursion needed. Note that EventTriggerAlterTableStart() should - * have been called. - */ - AlterTableInternal(RelationGetRelid(rel), atcmds, false); - CommandCounterIncrement(); - - tupDesc =3D RelationGetDescr(rel); - - /* now initialize the sequence's data */ - tuple =3D heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* now initialize the sequence table structure and its data */ + local_seq_init(rel, last_value, is_called); =20 /* process OWNED BY if given */ if (owned_by) @@ -292,10 +222,6 @@ ResetSequence(Oid seq_relid) { Relation seq_rel; SeqTable elm; - Form_pg_sequence_data seq; - Buffer buf; - HeapTupleData seqdatatuple; - HeapTuple tuple; HeapTuple pgstuple; Form_pg_sequence pgsform; int64 startv; @@ -306,7 +232,6 @@ ResetSequence(Oid seq_relid) * indeed a sequence. */ init_sequence(seq_relid, &elm, &seq_rel); - (void) read_seq_tuple(seq_rel, &buf, &seqdatatuple); =20 pgstuple =3D SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); if (!HeapTupleIsValid(pgstuple)) @@ -315,40 +240,8 @@ ResetSequence(Oid seq_relid) startv =3D pgsform->seqstart; ReleaseSysCache(pgstuple); =20 - /* - * Copy the existing sequence tuple. - */ - tuple =3D heap_copytuple(&seqdatatuple); - - /* Now we're done with the old page */ - UnlockReleaseBuffer(buf); - - /* - * Modify the copied tuple to execute the restart (compare the RESTART - * action in AlterSequence) - */ - seq =3D (Form_pg_sequence_data) GETSTRUCT(tuple); - seq->last_value =3D startv; - seq->is_called =3D false; - seq->log_cnt =3D 0; - - /* - * Create a new storage file for the sequence. - */ - RelationSetNewRelfilenumber(seq_rel, seq_rel->rd_rel->relpersistence); - - /* - * Ensure sequence's relfrozenxid is at 0, since it won't contain any - * unfrozen XIDs. Same with relminmxid, since a sequence will never - * contain multixacts. - */ - Assert(seq_rel->rd_rel->relfrozenxid =3D=3D InvalidTransactionId); - Assert(seq_rel->rd_rel->relminmxid =3D=3D InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - fill_seq_with_data(seq_rel, tuple); + /* Sequence state is forcibly reset here. */ + local_seq_reset(seq_rel, startv, false, true); =20 /* Clear local cache so that we don't think we have cached numbers */ /* Note that we do not change the currval() state */ @@ -357,106 +250,6 @@ ResetSequence(Oid seq_relid) sequence_close(seq_rel, NoLock); } =20 -/* - * Initialize a sequence's relation with the specified tuple as content - * - * This handles unlogged sequences by writing to both the main and the init - * fork as necessary. - */ -static void -fill_seq_with_data(Relation rel, HeapTuple tuple) -{ - fill_seq_fork_with_data(rel, tuple, MAIN_FORKNUM); - - if (rel->rd_rel->relpersistence =3D=3D RELPERSISTENCE_UNLOGGED) - { - SMgrRelation srel; - - srel =3D smgropen(rel->rd_locator, INVALID_PROC_NUMBER); - smgrcreate(srel, INIT_FORKNUM, false); - log_smgrcreate(&rel->rd_locator, INIT_FORKNUM); - fill_seq_fork_with_data(rel, tuple, INIT_FORKNUM); - FlushRelationBuffers(rel); - smgrclose(srel); - } -} - -/* - * Initialize a sequence's relation fork with the specified tuple as conte= nt - */ -static void -fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum) -{ - Buffer buf; - Page page; - sequence_magic *sm; - OffsetNumber offnum; - - /* Initialize first page of relation with special magic number */ - - buf =3D ExtendBufferedRel(BMR_REL(rel), forkNum, NULL, - EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - Assert(BufferGetBlockNumber(buf) =3D=3D 0); - - page =3D BufferGetPage(buf); - - PageInit(page, BufferGetPageSize(buf), sizeof(sequence_magic)); - sm =3D (sequence_magic *) PageGetSpecialPointer(page); - sm->magic =3D SEQ_MAGIC; - - /* Now insert sequence tuple */ - - /* - * Since VACUUM does not process sequences, we have to force the tuple to - * have xmin =3D FrozenTransactionId now. Otherwise it would become - * invisible to SELECTs after 2G transactions. It is okay to do this - * because if the current transaction aborts, no other xact will ever - * examine the sequence tuple anyway. - */ - HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId); - HeapTupleHeaderSetXminFrozen(tuple->t_data); - HeapTupleHeaderSetCmin(tuple->t_data, FirstCommandId); - HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId); - tuple->t_data->t_infomask |=3D HEAP_XMAX_INVALID; - ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber); - - /* check the comment above nextval_internal()'s equivalent call. */ - if (RelationNeedsWAL(rel)) - GetTopTransactionId(); - - START_CRIT_SECTION(); - - MarkBufferDirty(buf); - - offnum =3D PageAddItem(page, (Item) tuple->t_data, tuple->t_len, - InvalidOffsetNumber, false, false); - if (offnum !=3D FirstOffsetNumber) - elog(ERROR, "failed to add sequence tuple to page"); - - /* XLOG stuff */ - if (RelationNeedsWAL(rel) || forkNum =3D=3D INIT_FORKNUM) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator =3D rel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) tuple->t_data, tuple->t_len); - - recptr =3D XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); -} - /* * AlterSequence * @@ -468,10 +261,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) Oid relid; SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData datatuple; Form_pg_sequence seqform; - Form_pg_sequence_data newdataform; bool need_seq_rewrite; List *owned_by; ObjectAddress address; @@ -480,7 +270,6 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) bool reset_state =3D false; bool is_called; int64 last_value; - HeapTuple newdatatuple; =20 /* Open and lock sequence, and check for ownership along the way. */ relid =3D RangeVarGetRelidExtended(stmt->sequence, @@ -507,16 +296,8 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) =20 seqform =3D (Form_pg_sequence) GETSTRUCT(seqtuple); =20 - /* lock page buffer and read tuple into new sequence structure */ - (void) read_seq_tuple(seqrel, &buf, &datatuple); - - /* copy the existing sequence data tuple, so it can be modified locally */ - newdatatuple =3D heap_copytuple(&datatuple); - newdataform =3D (Form_pg_sequence_data) GETSTRUCT(newdatatuple); - last_value =3D newdataform->last_value; - is_called =3D newdataform->is_called; - - UnlockReleaseBuffer(buf); + /* Read sequence data */ + local_seq_get_state(seqrel, &last_value, &is_called); =20 /* Check and set new values */ init_params(pstate, stmt->options, stmt->for_identity, false, @@ -526,32 +307,10 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) /* If needed, rewrite the sequence relation itself */ if (need_seq_rewrite) { - /* check the comment above nextval_internal()'s equivalent call. */ if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); =20 - /* - * Create a new storage file for the sequence, making the state - * changes transactional. - */ - RelationSetNewRelfilenumber(seqrel, seqrel->rd_rel->relpersistence); - - /* - * Ensure sequence's relfrozenxid is at 0, since it won't contain any - * unfrozen XIDs. Same with relminmxid, since a sequence will never - * contain multixacts. - */ - Assert(seqrel->rd_rel->relfrozenxid =3D=3D InvalidTransactionId); - Assert(seqrel->rd_rel->relminmxid =3D=3D InvalidMultiXactId); - - /* - * Insert the modified tuple into the new storage file. - */ - newdataform->last_value =3D last_value; - newdataform->is_called =3D is_called; - if (reset_state) - newdataform->log_cnt =3D 0; - fill_seq_with_data(seqrel, newdatatuple); + local_seq_reset(seqrel, last_value, is_called, reset_state); } =20 /* Clear local cache so that we don't think we have cached numbers */ @@ -580,8 +339,6 @@ SequenceChangePersistence(Oid relid, char newrelpersist= ence) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; =20 init_sequence(relid, &elm, &seqrel); =20 @@ -589,10 +346,7 @@ SequenceChangePersistence(Oid relid, char newrelpersis= tence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); =20 - (void) read_seq_tuple(seqrel, &buf, &seqdatatuple); - RelationSetNewRelfilenumber(seqrel, newrelpersistence); - fill_seq_with_data(seqrel, &seqdatatuple); - UnlockReleaseBuffer(buf); + local_seq_change_persistence(seqrel, newrelpersistence); =20 sequence_close(seqrel, NoLock); } @@ -655,24 +409,15 @@ nextval_internal(Oid relid, bool check_permissions) { SeqTable elm; Relation seqrel; - Buffer buf; - Page page; HeapTuple pgstuple; Form_pg_sequence pgsform; - HeapTupleData seqdatatuple; - Form_pg_sequence_data seq; int64 incby, maxv, minv, cache, - log, - fetch, last; - int64 result, - next, - rescnt =3D 0; + int64 result; bool cycle; - bool logit =3D false; =20 /* open and lock sequence */ init_sequence(relid, &elm, &seqrel); @@ -717,105 +462,9 @@ nextval_internal(Oid relid, bool check_permissions) cycle =3D pgsform->seqcycle; ReleaseSysCache(pgstuple); =20 - /* lock page buffer and read tuple */ - seq =3D read_seq_tuple(seqrel, &buf, &seqdatatuple); - page =3D BufferGetPage(buf); - - last =3D next =3D result =3D seq->last_value; - fetch =3D cache; - log =3D seq->log_cnt; - - if (!seq->is_called) - { - rescnt++; /* return last_value if not is_called */ - fetch--; - } - - /* - * Decide whether we should emit a WAL log record. If so, force up the - * fetch count to grab SEQ_LOG_VALS more values than we actually need to - * cache. (These will then be usable without logging.) - * - * If this is the first nextval after a checkpoint, we must force a new - * WAL record to be written anyway, else replay starting from the - * checkpoint would fail to advance the sequence past the logged values. - * In this case we may as well fetch extra values. - */ - if (log < fetch || !seq->is_called) - { - /* forced log to satisfy local demand for values */ - fetch =3D log =3D fetch + SEQ_LOG_VALS; - logit =3D true; - } - else - { - XLogRecPtr redoptr =3D GetRedoRecPtr(); - - if (PageGetLSN(page) <=3D redoptr) - { - /* last update of seq was before checkpoint */ - fetch =3D log =3D fetch + SEQ_LOG_VALS; - logit =3D true; - } - } - - while (fetch) /* try to fetch cache [+ log ] numbers */ - { - /* - * Check MAXVALUE for ascending sequences and MINVALUE for descending - * sequences - */ - if (incby > 0) - { - /* ascending sequence */ - if ((maxv >=3D 0 && next > maxv - incby) || - (maxv < 0 && next + incby > maxv)) - { - if (rescnt > 0) - break; /* stop fetching */ - if (!cycle) - ereport(ERROR, - (errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED), - errmsg("nextval: reached maximum value of sequence \"%s\" (%lld)", - RelationGetRelationName(seqrel), - (long long) maxv))); - next =3D minv; - } - else - next +=3D incby; - } - else - { - /* descending sequence */ - if ((minv < 0 && next < minv - incby) || - (minv >=3D 0 && next + incby < minv)) - { - if (rescnt > 0) - break; /* stop fetching */ - if (!cycle) - ereport(ERROR, - (errcode(ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED), - errmsg("nextval: reached minimum value of sequence \"%s\" (%lld)", - RelationGetRelationName(seqrel), - (long long) minv))); - next =3D maxv; - } - else - next +=3D incby; - } - fetch--; - if (rescnt < cache) - { - log--; - rescnt++; - last =3D next; - if (rescnt =3D=3D 1) /* if it's first result - */ - result =3D next; /* it's what to return */ - } - } - - log -=3D fetch; /* adjust for any unfetched numbers */ - Assert(log >=3D 0); + /* retrieve next value from the access method */ + result =3D local_seq_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); =20 /* save info in local cache */ elm->increment =3D incby; @@ -825,69 +474,6 @@ nextval_internal(Oid relid, bool check_permissions) =20 last_used_seq =3D elm; =20 - /* - * If something needs to be WAL logged, acquire an xid, so this - * transaction's commit will trigger a WAL flush and wait for syncrep. - * It's sufficient to ensure the toplevel transaction has an xid, no need - * to assign xids subxacts, that'll already trigger an appropriate wait. - * (Have to do that here, so we're outside the critical section) - */ - if (logit && RelationNeedsWAL(seqrel)) - GetTopTransactionId(); - - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - /* - * We must mark the buffer dirty before doing XLogInsert(); see notes in - * SyncOneBuffer(). However, we don't apply the desired changes just yet. - * This looks like a violation of the buffer update protocol, but it is in - * fact safe because we hold exclusive lock on the buffer. Any other - * process, including a checkpoint, that tries to examine the buffer - * contents will block until we release the lock, and then will see the - * final state that we install below. - */ - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (logit && RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - - /* - * We don't log the current state of the tuple, but rather the state - * as it would appear after "log" more fetches. This lets us skip - * that many future WAL records, at the cost that we lose those - * sequence values if we crash. - */ - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - /* set values that will be saved in xlog */ - seq->last_value =3D next; - seq->is_called =3D true; - seq->log_cnt =3D 0; - - xlrec.locator =3D seqrel->rd_locator; - - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr =3D XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - /* Now update sequence tuple to the intended final state */ - seq->last_value =3D last; /* last fetched number */ - seq->is_called =3D true; - seq->log_cnt =3D log; /* how much is logged */ - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); - sequence_close(seqrel, NoLock); =20 return result; @@ -977,9 +563,6 @@ do_setval(Oid relid, int64 next, bool iscalled) { SeqTable elm; Relation seqrel; - Buffer buf; - HeapTupleData seqdatatuple; - Form_pg_sequence_data seq; HeapTuple pgstuple; Form_pg_sequence pgsform; int64 maxv, @@ -1013,9 +596,6 @@ do_setval(Oid relid, int64 next, bool iscalled) */ PreventCommandIfParallelMode("setval()"); =20 - /* lock page buffer and read tuple */ - seq =3D read_seq_tuple(seqrel, &buf, &seqdatatuple); - if ((next < minv) || (next > maxv)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), @@ -1037,37 +617,8 @@ do_setval(Oid relid, int64 next, bool iscalled) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); =20 - /* ready to change the on-disk (or really, in-buffer) tuple */ - START_CRIT_SECTION(); - - seq->last_value =3D next; /* last fetched number */ - seq->is_called =3D iscalled; - seq->log_cnt =3D 0; - - MarkBufferDirty(buf); - - /* XLOG stuff */ - if (RelationNeedsWAL(seqrel)) - { - xl_seq_rec xlrec; - XLogRecPtr recptr; - Page page =3D BufferGetPage(buf); - - XLogBeginInsert(); - XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); - - xlrec.locator =3D seqrel->rd_locator; - XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec)); - XLogRegisterData((char *) seqdatatuple.t_data, seqdatatuple.t_len); - - recptr =3D XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG); - - PageSetLSN(page, recptr); - } - - END_CRIT_SECTION(); - - UnlockReleaseBuffer(buf); + /* Call the access method callback */ + local_seq_setval(seqrel, next, iscalled); =20 sequence_close(seqrel, NoLock); } @@ -1208,62 +759,6 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p= _rel) } =20 =20 -/* - * Given an opened sequence relation, lock the page buffer and find the tu= ple - * - * *buf receives the reference to the pinned-and-ex-locked buffer - * *seqdatatuple receives the reference to the sequence tuple proper - * (this arg should point to a local variable of type HeapTupleData) - * - * Function's return value points to the data payload of the tuple - */ -static Form_pg_sequence_data -read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) -{ - Page page; - ItemId lp; - sequence_magic *sm; - Form_pg_sequence_data seq; - - *buf =3D ReadBuffer(rel, 0); - LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); - - page =3D BufferGetPage(*buf); - sm =3D (sequence_magic *) PageGetSpecialPointer(page); - - if (sm->magic !=3D SEQ_MAGIC) - elog(ERROR, "bad magic number in sequence \"%s\": %08X", - RelationGetRelationName(rel), sm->magic); - - lp =3D PageGetItemId(page, FirstOffsetNumber); - Assert(ItemIdIsNormal(lp)); - - /* Note we currently only bother to set these two fields of *seqdatatuple= */ - seqdatatuple->t_data =3D (HeapTupleHeader) PageGetItem(page, lp); - seqdatatuple->t_len =3D ItemIdGetLength(lp); - - /* - * Previous releases of Postgres neglected to prevent SELECT FOR UPDATE on - * a sequence, which would leave a non-frozen XID in the sequence tuple's - * xmax, which eventually leads to clog access failures or worse. If we - * see this has happened, clean up after it. We treat this like a hint - * bit update, ie, don't bother to WAL-log it, since we can certainly do - * this again if the update gets lost. - */ - Assert(!(seqdatatuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)); - if (HeapTupleHeaderGetRawXmax(seqdatatuple->t_data) !=3D InvalidTransacti= onId) - { - HeapTupleHeaderSetXmax(seqdatatuple->t_data, InvalidTransactionId); - seqdatatuple->t_data->t_infomask &=3D ~HEAP_XMAX_COMMITTED; - seqdatatuple->t_data->t_infomask |=3D HEAP_XMAX_INVALID; - MarkBufferDirtyHint(*buf, true); - } - - seq =3D (Form_pg_sequence_data) GETSTRUCT(seqdatatuple); - - return seq; -} - /* * init_params: process the options list of CREATE or ALTER SEQUENCE, and * store the values into appropriate fields of seqform, for changes that go @@ -1823,9 +1318,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) SeqTable elm; Relation seqrel; TupleDesc tupdesc; - Buffer buf; - HeapTupleData seqtuple; - Form_pg_sequence_data seq; bool is_called; int64 last_value; =20 @@ -1842,12 +1334,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); =20 - seq =3D read_seq_tuple(seqrel, &buf, &seqtuple); - - is_called =3D seq->is_called; - last_value =3D seq->last_value; - - UnlockReleaseBuffer(buf); + local_seq_get_state(seqrel, &last_value, &is_called); sequence_close(seqrel, NoLock); =20 values[0] =3D BoolGetDatum(is_called); @@ -1855,57 +1342,6 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)= )); } =20 - -void -seq_redo(XLogReaderState *record) -{ - XLogRecPtr lsn =3D record->EndRecPtr; - uint8 info =3D XLogRecGetInfo(record) & ~XLR_INFO_MASK; - Buffer buffer; - Page page; - Page localpage; - char *item; - Size itemsz; - xl_seq_rec *xlrec =3D (xl_seq_rec *) XLogRecGetData(record); - sequence_magic *sm; - - if (info !=3D XLOG_SEQ_LOG) - elog(PANIC, "seq_redo: unknown op code %u", info); - - buffer =3D XLogInitBufferForRedo(record, 0); - page =3D (Page) BufferGetPage(buffer); - - /* - * We always reinit the page. However, since this WAL record type is also - * used for updating sequences, it's possible that a hot-standby backend - * is examining the page concurrently; so we mustn't transiently trash the - * buffer. The solution is to build the correct new page contents in - * local workspace and then memcpy into the buffer. Then only bytes that - * are supposed to change will change, even transiently. We must palloc - * the local page for alignment reasons. - */ - localpage =3D (Page) palloc(BufferGetPageSize(buffer)); - - PageInit(localpage, BufferGetPageSize(buffer), sizeof(sequence_magic)); - sm =3D (sequence_magic *) PageGetSpecialPointer(localpage); - sm->magic =3D SEQ_MAGIC; - - item =3D (char *) xlrec + sizeof(xl_seq_rec); - itemsz =3D XLogRecGetDataLen(record) - sizeof(xl_seq_rec); - - if (PageAddItem(localpage, (Item) item, itemsz, - FirstOffsetNumber, false, false) =3D=3D InvalidOffsetNumber) - elog(PANIC, "seq_redo: failed to add item to page"); - - PageSetLSN(localpage, lsn); - - memcpy(page, localpage, BufferGetPageSize(buffer)); - MarkBufferDirty(buffer); - UnlockReleaseBuffer(buffer); - - pfree(localpage); -} - /* * Flush cached sequence information. */ @@ -1920,14 +1356,3 @@ ResetSequenceCaches(void) =20 last_used_seq =3D NULL; } - -/* - * Mask a Sequence page before performing consistency checks on it. - */ -void -seq_mask(char *page, BlockNumber blkno) -{ - mask_page_lsn_and_checksum(page); - - mask_unused_space(page); -} diff --git a/src/bin/pg_waldump/.gitignore b/src/bin/pg_waldump/.gitignore index ec51f41c76..0f45509f2c 100644 --- a/src/bin/pg_waldump/.gitignore +++ b/src/bin/pg_waldump/.gitignore @@ -10,13 +10,13 @@ /gistdesc.c /hashdesc.c /heapdesc.c +/localseqdesc.c /logicalmsgdesc.c /mxactdesc.c /nbtdesc.c /relmapdesc.c /replorigindesc.c /rmgrdesc_utils.c -/seqdesc.c /smgrdesc.c /spgdesc.c /standbydesc.c diff --git a/src/bin/pg_waldump/rmgrdesc.c b/src/bin/pg_waldump/rmgrdesc.c index 6b8c17bb4c..ff09335607 100644 --- a/src/bin/pg_waldump/rmgrdesc.c +++ b/src/bin/pg_waldump/rmgrdesc.c @@ -16,6 +16,7 @@ #include "access/gistxlog.h" #include "access/hash_xlog.h" #include "access/heapam_xlog.h" +#include "access/localam.h" #include "access/multixact.h" #include "access/nbtxlog.h" #include "access/rmgr.h" --=20 2.43.0 --OKPlUpZv0l0WYI+j Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0005-Sequence-access-methods-backend-support.patch" Content-Transfer-Encoding: quoted-printable =46rom 4363bf08870e9e1e53759ecdc1217bc31a701465 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Fri, 19 Apr 2024 15:26:29 +0900 Subject: [PATCH v4 5/8] Sequence access methods - backend support The "local" sequence AM is now plugged in as a handler in the relcache, and a set of callbacks in sequenceam.h. --- src/include/access/localam.h | 15 -- src/include/access/sequenceam.h | 188 ++++++++++++++++++ src/include/catalog/pg_am.dat | 3 + src/include/catalog/pg_am.h | 1 + src/include/catalog/pg_class.h | 6 + src/include/catalog/pg_proc.dat | 13 ++ src/include/catalog/pg_type.dat | 6 + src/include/commands/defrem.h | 1 + src/include/commands/sequence.h | 34 ---- src/include/nodes/meson.build | 1 + src/include/nodes/parsenodes.h | 1 + src/include/utils/guc_hooks.h | 2 + src/include/utils/rel.h | 5 + src/backend/access/sequence/Makefile | 2 +- src/backend/access/sequence/local.c | 69 ++++--- src/backend/access/sequence/meson.build | 1 + src/backend/access/sequence/sequence.c | 3 +- src/backend/access/sequence/sequenceamapi.c | 145 ++++++++++++++ src/backend/catalog/heap.c | 6 +- src/backend/commands/amcmds.c | 16 ++ src/backend/commands/sequence.c | 21 +- src/backend/commands/tablecmds.c | 17 +- src/backend/nodes/Makefile | 1 + src/backend/nodes/gen_node_support.pl | 2 + src/backend/parser/gram.y | 12 +- src/backend/parser/parse_utilcmd.c | 2 + src/backend/utils/adt/pseudotypes.c | 1 + src/backend/utils/cache/relcache.c | 91 +++++++-- src/backend/utils/misc/guc_tables.c | 12 ++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/bin/pg_waldump/t/001_basic.pl | 2 +- src/bin/psql/describe.c | 2 + src/bin/psql/tab-complete.c | 4 +- src/test/regress/expected/create_am.out | 33 ++- src/test/regress/expected/opr_sanity.out | 12 ++ src/test/regress/expected/psql.out | 64 +++--- src/test/regress/sql/create_am.sql | 24 ++- src/test/regress/sql/opr_sanity.sql | 10 + src/tools/pgindent/typedefs.list | 5 +- 39 files changed, 677 insertions(+), 157 deletions(-) create mode 100644 src/include/access/sequenceam.h create mode 100644 src/backend/access/sequence/sequenceamapi.c diff --git a/src/include/access/localam.h b/src/include/access/localam.h index 5b0575dc2e..7afc0a9636 100644 --- a/src/include/access/localam.h +++ b/src/include/access/localam.h @@ -15,7 +15,6 @@ =20 #include "access/xlogreader.h" #include "storage/relfilelocator.h" -#include "utils/rel.h" =20 /* XLOG stuff */ #define XLOG_LOCAL_SEQ_LOG 0x00 @@ -31,18 +30,4 @@ extern void local_seq_desc(StringInfo buf, XLogReaderSta= te *record); extern const char *local_seq_identify(uint8 info); extern void local_seq_mask(char *page, BlockNumber blkno); =20 -/* access routines */ -extern int64 local_seq_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last); -extern const char *local_seq_get_table_am(void); -extern void local_seq_init(Relation rel, int64 last_value, bool is_called); -extern void local_seq_setval(Relation rel, int64 next, bool iscalled); -extern void local_seq_reset(Relation rel, int64 startv, bool is_called, - bool reset_state); -extern void local_seq_get_state(Relation rel, int64 *last_value, - bool *is_called); -extern void local_seq_change_persistence(Relation rel, - char newrelpersistence); - #endif /* LOCALAM_H */ diff --git a/src/include/access/sequenceam.h b/src/include/access/sequencea= m.h new file mode 100644 index 0000000000..f0f36c6c7e --- /dev/null +++ b/src/include/access/sequenceam.h @@ -0,0 +1,188 @@ +/*------------------------------------------------------------------------- + * + * sequenceam.h + * POSTGRES sequence access method definitions. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/sequenceam.h + * + * NOTES + * See sequenceam.sgml for higher level documentation. + * + *------------------------------------------------------------------------- + */ +#ifndef SEQUENCEAM_H +#define SEQUENCEAM_H + +#include "utils/rel.h" + +#define DEFAULT_SEQUENCE_ACCESS_METHOD "local" + +/* GUCs */ +extern PGDLLIMPORT char *default_sequence_access_method; + +/* + * API struct for a sequence AM. Note this must be allocated in a + * server-lifetime manner, typically as a static const struct, which then = gets + * returned by FormData_pg_am.amhandler. + * + * In most cases it's not appropriate to call the callbacks directly, use = the + * sequence_* wrapper functions instead. + * + * GetSequenceAmRoutine() asserts that required callbacks are filled in, + * remember to update when adding a callback. + */ +typedef struct SequenceAmRoutine +{ + /* this must be set to T_SequenceAmRoutine */ + NodeTag type; + + /* + * Retrieve table access method used by a sequence to store its metadata. + */ + const char *(*get_table_am) (void); + + /* + * Initialize sequence after creating a sequence Relation in pg_class, + * setting up the sequence for use. "last_value" and "is_called" are + * guessed from the options set for the sequence in CREATE SEQUENCE, based + * on the configuration of pg_sequence. + */ + void (*init) (Relation rel, int64 last_value, bool is_called); + + /* + * Retrieve a result for nextval(), based on the options retrieved from + * the sequence's options in pg_sequence. "last" is the last value + * calculated stored in the session's local cache, for lastval(). + */ + int64 (*nextval) (Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last); + + /* + * Callback to set the state of a sequence, based on the input arguments + * from setval(). + */ + void (*setval) (Relation rel, int64 next, bool iscalled); + + /* + * Reset a sequence to its initial value. "reset_state", if set to true, + * means that the sequence parameters have changed, hence its internal + * state may need to be reset as well. "startv" and "is_called" are + * values guessed from the configuration of the sequence, based on the + * contents of pg_sequence. + */ + void (*reset) (Relation rel, int64 startv, bool is_called, + bool reset_state); + + /* + * Returns the current state of a sequence, returning data for + * pg_sequence_last_value() and related DDLs like ALTER SEQUENCE. + * "last_value" and "is_called" should be assigned to the values retrieved + * from the sequence Relation. + */ + void (*get_state) (Relation rel, int64 *last_value, bool *is_called); + + /* + * Callback used when switching persistence of a sequence Relation, to + * reset the sequence based on its new persistence "newrelpersistence". + */ + void (*change_persistence) (Relation rel, char newrelpersistence); + +} SequenceAmRoutine; + + +/* -----------------------------------------------------------------------= ---- + * Wrapper functions for each callback. + * -----------------------------------------------------------------------= ---- + */ + +/* + * Returns the name of the table access method used by this sequence. + */ +static inline const char * +sequence_get_table_am(Relation rel) +{ + return rel->rd_sequenceam->get_table_am(); +} + +/* + * Insert tuple data based on the information guessed from the contents + * of pg_sequence. + */ +static inline void +sequence_init(Relation rel, int64 last_value, bool is_called) +{ + rel->rd_sequenceam->init(rel, last_value, is_called); +} + +/* + * Allocate a set of values for the given sequence. "last" is the last va= lue + * allocated. The result returned is the next value of the sequence compu= ted. + */ +static inline int64 +sequence_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + return rel->rd_sequenceam->nextval(rel, incby, maxv, minv, cache, + cycle, last); +} + +/* + * Callback to set the state of a sequence, based on the input arguments + * from setval(). + */ +static inline void +sequence_setval(Relation rel, int64 next, bool iscalled) +{ + rel->rd_sequenceam->setval(rel, next, iscalled); +} + +/* + * Reset a sequence to its initial state. + */ +static inline void +sequence_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + rel->rd_sequenceam->reset(rel, startv, is_called, reset_state); +} + +/* + * Retrieve sequence metadata. + */ +static inline void +sequence_get_state(Relation rel, int64 *last_value, bool *is_called) +{ + rel->rd_sequenceam->get_state(rel, last_value, is_called); +} + +/* + * Callback to change the persistence of a sequence Relation. + */ +static inline void +sequence_change_persistence(Relation rel, char newrelpersistence) +{ + rel->rd_sequenceam->change_persistence(rel, newrelpersistence); +} + +/* -----------------------------------------------------------------------= ----- + * Functions in sequenceamapi.c + * -----------------------------------------------------------------------= ----- + */ + +extern const SequenceAmRoutine *GetSequenceAmRoutine(Oid amhandler); +extern Oid GetSequenceAmRoutineId(Oid amoid); + +/* -----------------------------------------------------------------------= ----- + * Functions in local.c + * -----------------------------------------------------------------------= ----- + */ + +extern const SequenceAmRoutine *GetLocalSequenceAmRoutine(void); + +#endif /* SEQUENCEAM_H */ diff --git a/src/include/catalog/pg_am.dat b/src/include/catalog/pg_am.dat index db87490282..3b31f9df59 100644 --- a/src/include/catalog/pg_am.dat +++ b/src/include/catalog/pg_am.dat @@ -15,6 +15,9 @@ { oid =3D> '2', oid_symbol =3D> 'HEAP_TABLE_AM_OID', descr =3D> 'heap table access method', amname =3D> 'heap', amhandler =3D> 'heap_tableam_handler', amtype =3D> '= t' }, +{ oid =3D> '8048', oid_symbol =3D> 'LOCAL_SEQUENCE_AM_OID', + descr =3D> 'local sequence access method', + amname =3D> 'local', amhandler =3D> 'local_sequenceam_handler', amtype = =3D> 's' }, { oid =3D> '403', oid_symbol =3D> 'BTREE_AM_OID', descr =3D> 'b-tree index access method', amname =3D> 'btree', amhandler =3D> 'bthandler', amtype =3D> 'i' }, diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h index 475593fad4..3a94e8c636 100644 --- a/src/include/catalog/pg_am.h +++ b/src/include/catalog/pg_am.h @@ -59,6 +59,7 @@ MAKE_SYSCACHE(AMOID, pg_am_oid_index, 4); * Allowed values for amtype */ #define AMTYPE_INDEX 'i' /* index access method */ +#define AMTYPE_SEQUENCE 's' /* sequence access method */ #define AMTYPE_TABLE 't' /* table access method */ =20 #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 0fc2c093b0..21623f34fa 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -228,6 +228,12 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, = 128); (relkind) =3D=3D RELKIND_TOASTVALUE || \ (relkind) =3D=3D RELKIND_MATVIEW) =20 +/* + * Relation kinds with a sequence access method (rd_sequenceam). + */ +#define RELKIND_HAS_SEQUENCE_AM(relkind) \ + ((relkind) =3D=3D RELKIND_SEQUENCE) + extern int errdetail_relkind_not_supported(char relkind); =20 #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.= dat index 1a8c8ebba5..252323fb78 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -913,6 +913,12 @@ prorettype =3D> 'table_am_handler', proargtypes =3D> 'internal', prosrc =3D> 'heap_tableam_handler' }, =20 +# Sequence access method handlers +{ oid =3D> '8209', descr =3D> 'local sequence access method handler', + proname =3D> 'local_sequenceam_handler', provolatile =3D> 'v', + prorettype =3D> 'sequence_am_handler', proargtypes =3D> 'internal', + prosrc =3D> 'local_sequenceam_handler' }, + # Index access method handlers { oid =3D> '330', descr =3D> 'btree index access method handler', proname =3D> 'bthandler', provolatile =3D> 'v', prorettype =3D> 'index_a= m_handler', @@ -7627,6 +7633,13 @@ { oid =3D> '327', descr =3D> 'I/O', proname =3D> 'index_am_handler_out', prorettype =3D> 'cstring', proargtypes =3D> 'index_am_handler', prosrc =3D> 'index_am_handler_out' = }, +{ oid =3D> '8207', descr =3D> 'I/O', + proname =3D> 'sequence_am_handler_in', proisstrict =3D> 'f', + prorettype =3D> 'sequence_am_handler', proargtypes =3D> 'cstring', + prosrc =3D> 'sequence_am_handler_in' }, +{ oid =3D> '8208', descr =3D> 'I/O', + proname =3D> 'sequence_am_handler_out', prorettype =3D> 'cstring', + proargtypes =3D> 'sequence_am_handler', prosrc =3D> 'sequence_am_handler= _out' }, { oid =3D> '3311', descr =3D> 'I/O', proname =3D> 'tsm_handler_in', proisstrict =3D> 'f', prorettype =3D> 'ts= m_handler', proargtypes =3D> 'cstring', prosrc =3D> 'tsm_handler_in' }, diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.= dat index d29194da31..8f00b322ec 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -626,6 +626,12 @@ typcategory =3D> 'P', typinput =3D> 'index_am_handler_in', typoutput =3D> 'index_am_handler_out', typreceive =3D> '-', typsend =3D>= '-', typalign =3D> 'i' }, +{ oid =3D> '8210', + descr =3D> 'pseudo-type for the result of a sequence AM handler function= ', + typname =3D> 'sequence_am_handler', typlen =3D> '4', typbyval =3D> 't', + typtype =3D> 'p', typcategory =3D> 'P', typinput =3D> 'sequence_am_handl= er_in', + typoutput =3D> 'sequence_am_handler_out', typreceive =3D> '-', typsend = =3D> '-', + typalign =3D> 'i' }, { oid =3D> '3310', descr =3D> 'pseudo-type for the result of a tablesample method function', typname =3D> 'tsm_handler', typlen =3D> '4', typbyval =3D> 't', typtype = =3D> 'p', diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 5fd095ea17..a43a6cbf85 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -145,6 +145,7 @@ extern Datum transformGenericOptions(Oid catalogId, extern ObjectAddress CreateAccessMethod(CreateAmStmt *stmt); extern Oid get_index_am_oid(const char *amname, bool missing_ok); extern Oid get_table_am_oid(const char *amname, bool missing_ok); +extern Oid get_sequence_am_oid(const char *amname, bool missing_ok); extern Oid get_am_oid(const char *amname, bool missing_ok); extern char *get_am_name(Oid amOid); =20 diff --git a/src/include/commands/sequence.h b/src/include/commands/sequenc= e.h index e88cbee3b5..9b22b2f248 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -22,35 +22,6 @@ #include "storage/relfilelocator.h" =20 =20 -typedef struct FormData_pg_sequence_data -{ - int64 last_value; - int64 log_cnt; - bool is_called; -} FormData_pg_sequence_data; - -typedef FormData_pg_sequence_data *Form_pg_sequence_data; - -/* - * Columns of a sequence relation - */ - -#define SEQ_COL_LASTVAL 1 -#define SEQ_COL_LOG 2 -#define SEQ_COL_CALLED 3 - -#define SEQ_COL_FIRSTCOL SEQ_COL_LASTVAL -#define SEQ_COL_LASTCOL SEQ_COL_CALLED - -/* XLOG stuff */ -#define XLOG_SEQ_LOG 0x00 - -typedef struct xl_seq_rec -{ - RelFileLocator locator; - /* SEQUENCE TUPLE DATA FOLLOWS AT THE END */ -} xl_seq_rec; - extern int64 nextval_internal(Oid relid, bool check_permissions); extern Datum nextval(PG_FUNCTION_ARGS); extern List *sequence_options(Oid relid); @@ -62,9 +33,4 @@ extern void DeleteSequenceTuple(Oid relid); extern void ResetSequence(Oid seq_relid); extern void ResetSequenceCaches(void); =20 -extern void seq_redo(XLogReaderState *record); -extern void seq_desc(StringInfo buf, XLogReaderState *record); -extern const char *seq_identify(uint8 info); -extern void seq_mask(char *page, BlockNumber blkno); - #endif /* SEQUENCE_H */ diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build index b665e55b65..774ae2652c 100644 --- a/src/include/nodes/meson.build +++ b/src/include/nodes/meson.build @@ -9,6 +9,7 @@ node_support_input_i =3D [ 'nodes/execnodes.h', 'access/amapi.h', 'access/sdir.h', + 'access/sequenceam.h', 'access/tableam.h', 'access/tsmapi.h', 'commands/event_trigger.h', diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index cfe73cd737..47f0143746 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3133,6 +3133,7 @@ typedef struct CreateSeqStmt List *options; Oid ownerId; /* ID of owner, or InvalidOid for default */ bool for_identity; + char *accessMethod; /* USING name of access method (eg. local) */ bool if_not_exists; /* just do nothing if it already exists? */ } CreateSeqStmt; =20 diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index d64dc5fcdb..539ca97003 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -55,6 +55,8 @@ extern bool check_debug_io_direct(char **newval, void **e= xtra, GucSource source) extern void assign_debug_io_direct(const char *newval, void *extra); extern bool check_default_table_access_method(char **newval, void **extra, GucSource source); +extern bool check_default_sequence_access_method(char **newval, void **ext= ra, + GucSource source); extern bool check_default_tablespace(char **newval, void **extra, GucSource source); extern bool check_default_text_search_config(char **newval, void **extra, = GucSource source); diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 8700204953..2a4b7bed2b 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -188,6 +188,11 @@ typedef struct RelationData */ const struct TableAmRoutine *rd_tableam; =20 + /* + * Sequence access method. + */ + const struct SequenceAmRoutine *rd_sequenceam; + /* These are non-NULL only for an index relation: */ Form_pg_index rd_index; /* pg_index tuple describing this index */ /* use "struct" here to avoid needing to include htup.h: */ diff --git a/src/backend/access/sequence/Makefile b/src/backend/access/sequ= ence/Makefile index 697f89905e..b89a7e0526 100644 --- a/src/backend/access/sequence/Makefile +++ b/src/backend/access/sequence/Makefile @@ -12,6 +12,6 @@ subdir =3D src/backend/access/sequence top_builddir =3D ../../../.. include $(top_builddir)/src/Makefile.global =20 -OBJS =3D local.o sequence.o +OBJS =3D local.o sequence.o sequenceamapi.o =20 include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/sequence/local.c b/src/backend/access/seque= nce/local.c index ac124095fb..efc655f9f5 100644 --- a/src/backend/access/sequence/local.c +++ b/src/backend/access/sequence/local.c @@ -18,6 +18,7 @@ #include "access/bufmask.h" #include "access/localam.h" #include "access/multixact.h" +#include "access/sequenceam.h" #include "access/xact.h" #include "access/xloginsert.h" #include "access/xlogutils.h" @@ -25,6 +26,7 @@ #include "commands/tablecmds.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "utils/builtins.h" =20 =20 /* @@ -297,15 +299,15 @@ local_seq_redo(XLogReaderState *record) } =20 /* - * local_seq_nextval() + * local_nextval() * * Allocate a new value for a local sequence, based on the sequence * configuration. */ -int64 -local_seq_nextval(Relation rel, int64 incby, int64 maxv, - int64 minv, int64 cache, bool cycle, - int64 *last) +static int64 +local_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) { int64 result; int64 fetch; @@ -485,18 +487,18 @@ local_seq_nextval(Relation rel, int64 incby, int64 ma= xv, } =20 /* - * local_seq_get_table_am() + * local_get_table_am() * * Return the table access method used by this sequence. */ -const char * -local_seq_get_table_am(void) +static const char * +local_get_table_am(void) { return "heap"; } =20 /* - * local_seq_init() + * local_init() * * Add the sequence attributes to the relation created for this sequence * AM and insert a tuple of metadata into the sequence relation, based on @@ -504,8 +506,8 @@ local_seq_get_table_am(void) * inserted after the relation has been created, filling in its heap * table. */ -void -local_seq_init(Relation rel, int64 last_value, bool is_called) +static void +local_init(Relation rel, int64 last_value, bool is_called) { Datum value[SEQ_COL_LASTCOL]; bool null[SEQ_COL_LASTCOL]; @@ -567,12 +569,12 @@ local_seq_init(Relation rel, int64 last_value, bool i= s_called) } =20 /* - * local_seq_setval() + * local_setval() * * Callback for setval(). */ -void -local_seq_setval(Relation rel, int64 next, bool iscalled) +static void +local_setval(Relation rel, int64 next, bool iscalled) { Buffer buf; HeapTupleData seqdatatuple; @@ -614,13 +616,13 @@ local_seq_setval(Relation rel, int64 next, bool iscal= led) } =20 /* - * local_seq_reset() + * local_reset() * * Perform a hard reset on the local sequence, rewriting its heap data * entirely. */ -void -local_seq_reset(Relation rel, int64 startv, bool is_called, bool reset_sta= te) +static void +local_reset(Relation rel, int64 startv, bool is_called, bool reset_state) { Form_pg_sequence_data seq; Buffer buf; @@ -668,12 +670,12 @@ local_seq_reset(Relation rel, int64 startv, bool is_c= alled, bool reset_state) } =20 /* - * local_seq_get_state() + * local_get_state() * * Retrieve the state of a local sequence. */ -void -local_seq_get_state(Relation rel, int64 *last_value, bool *is_called) +static void +local_get_state(Relation rel, int64 *last_value, bool *is_called) { Buffer buf; HeapTupleData seqdatatuple; @@ -689,12 +691,12 @@ local_seq_get_state(Relation rel, int64 *last_value, = bool *is_called) } =20 /* - * local_seq_change_persistence() + * local_change_persistence() * * Persistence change for the local sequence Relation. */ -void -local_seq_change_persistence(Relation rel, char newrelpersistence) +static void +local_change_persistence(Relation rel, char newrelpersistence) { Buffer buf; HeapTupleData seqdatatuple; @@ -704,3 +706,24 @@ local_seq_change_persistence(Relation rel, char newrel= persistence) fill_seq_with_data(rel, &seqdatatuple); UnlockReleaseBuffer(buf); } + +/* ------------------------------------------------------------------------ + * Definition of the local sequence access method. + * ------------------------------------------------------------------------ + */ +static const SequenceAmRoutine local_methods =3D { + .type =3D T_SequenceAmRoutine, + .get_table_am =3D local_get_table_am, + .init =3D local_init, + .nextval =3D local_nextval, + .setval =3D local_setval, + .reset =3D local_reset, + .get_state =3D local_get_state, + .change_persistence =3D local_change_persistence +}; + +Datum +local_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&local_methods); +} diff --git a/src/backend/access/sequence/meson.build b/src/backend/access/s= equence/meson.build index 8c4ef42467..9baab19512 100644 --- a/src/backend/access/sequence/meson.build +++ b/src/backend/access/sequence/meson.build @@ -3,4 +3,5 @@ backend_sources +=3D files( 'local.c', 'sequence.c', + 'sequenceamapi.c', ) diff --git a/src/backend/access/sequence/sequence.c b/src/backend/access/se= quence/sequence.c index 8d6b7bb5dc..5e7d76d3b0 100644 --- a/src/backend/access/sequence/sequence.c +++ b/src/backend/access/sequence/sequence.c @@ -13,7 +13,8 @@ * * NOTES * This file contains sequence_ routines that implement access to sequen= ces - * (in contrast to other relation types like indexes). + * (in contrast to other relation types like indexes) that are independe= nt + * of individual sequence access methods. * *------------------------------------------------------------------------- */ diff --git a/src/backend/access/sequence/sequenceamapi.c b/src/backend/acce= ss/sequence/sequenceamapi.c new file mode 100644 index 0000000000..4314b9ecb5 --- /dev/null +++ b/src/backend/access/sequence/sequenceamapi.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * sequenceamapi.c + * general sequence access method routines + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/sequence/sequenceamapi.c + * + * + * Sequence access method allows the SQL Standard Sequence objects to be + * managed according to either the default access method or a pluggable + * replacement. Each sequence can only use one access method at a time, + * though different sequence access methods can be in use by different + * sequences at the same time. + * + * -----------------------------------------------------------------------= -- + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "access/sequenceam.h" +#include "catalog/pg_am.h" +#include "commands/defrem.h" +#include "miscadmin.h" +#include "utils/guc_hooks.h" +#include "utils/syscache.h" + + +/* GUC */ +char *default_sequence_access_method =3D DEFAULT_SEQUENCE_ACCESS_METHOD; + +/* + * GetSequenceAmRoutine + * Call the specified access method handler routine to get its + * SequenceAmRoutine struct, which will be palloc'd in the caller's + * memory context. + */ +const SequenceAmRoutine * +GetSequenceAmRoutine(Oid amhandler) +{ + Datum datum; + SequenceAmRoutine *routine; + + datum =3D OidFunctionCall0(amhandler); + routine =3D (SequenceAmRoutine *) DatumGetPointer(datum); + + if (routine =3D=3D NULL || !IsA(routine, SequenceAmRoutine)) + elog(ERROR, "sequence access method handler %u did not return a Sequence= AmRoutine struct", + amhandler); + + /* + * Assert that all required callbacks are present. That makes it a bit + * easier to keep AMs up to date, e.g. when forward porting them to a new + * major version. + */ + Assert(routine->get_table_am !=3D NULL); + Assert(routine->init !=3D NULL); + Assert(routine->nextval !=3D NULL); + Assert(routine->setval !=3D NULL); + Assert(routine->reset !=3D NULL); + Assert(routine->get_state !=3D NULL); + Assert(routine->change_persistence !=3D NULL); + + return routine; +} + +/* + * GetSequenceAmRoutineId + * Call pg_am and retrieve the OID of the access method handler. + */ +Oid +GetSequenceAmRoutineId(Oid amoid) +{ + Oid amhandleroid; + HeapTuple tuple; + Form_pg_am aform; + + tuple =3D SearchSysCache1(AMOID, + ObjectIdGetDatum(amoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", amoid); + aform =3D (Form_pg_am) GETSTRUCT(tuple); + Assert(aform->amtype =3D=3D AMTYPE_SEQUENCE); + amhandleroid =3D aform->amhandler; + ReleaseSysCache(tuple); + + return amhandleroid; +} + +/* check_hook: validate new default_sequence_access_method */ +bool +check_default_sequence_access_method(char **newval, void **extra, + GucSource source) +{ + if (**newval =3D=3D '\0') + { + GUC_check_errdetail("%s cannot be empty.", + "default_sequence_access_method"); + return false; + } + + if (strlen(*newval) >=3D NAMEDATALEN) + { + GUC_check_errdetail("%s is too long (maximum %d characters).", + "default_sequence_access_method", NAMEDATALEN - 1); + return false; + } + + /* + * If we aren't inside a transaction, or not connected to a database, we + * cannot do the catalog access necessary to verify the method. Must + * accept the value on faith. + */ + if (IsTransactionState() && MyDatabaseId !=3D InvalidOid) + { + if (!OidIsValid(get_sequence_am_oid(*newval, true))) + { + /* + * When source =3D=3D PGC_S_TEST, don't throw a hard error for a + * nonexistent sequence access method, only a NOTICE. See comments + * in guc.h. + */ + if (source =3D=3D PGC_S_TEST) + { + ereport(NOTICE, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("sequence access method \"%s\" does not exist", + *newval))); + } + else + { + GUC_check_errdetail("sequence access method \"%s\" does not exist.", + *newval); + return false; + } + } + } + + return true; +} diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index f0278b9c01..bca2259e4b 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -1464,8 +1464,12 @@ heap_create_with_catalog(const char *relname, * * No need to add an explicit dependency for the toast table, as the * main table depends on it. + * + * Sequences and tables are created with their access method ID + * given by the caller of this function. */ - if (RELKIND_HAS_TABLE_AM(relkind) && relkind !=3D RELKIND_TOASTVALUE) + if ((RELKIND_HAS_TABLE_AM(relkind) && relkind !=3D RELKIND_TOASTVALUE) || + RELKIND_HAS_SEQUENCE_AM(relkind)) { ObjectAddressSet(referenced, AccessMethodRelationId, accessmtd); add_exact_object_address(&referenced, addrs); diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c index aaa0f9a1dc..1a27f191a3 100644 --- a/src/backend/commands/amcmds.c +++ b/src/backend/commands/amcmds.c @@ -15,6 +15,7 @@ =20 #include "access/htup_details.h" #include "access/table.h" +#include "access/sequenceam.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" @@ -175,6 +176,16 @@ get_table_am_oid(const char *amname, bool missing_ok) return get_am_type_oid(amname, AMTYPE_TABLE, missing_ok); } =20 +/* + * get_sequence_am_oid - given an access method name, look up its OID + * and verify it corresponds to an sequence AM. + */ +Oid +get_sequence_am_oid(const char *amname, bool missing_ok) +{ + return get_am_type_oid(amname, AMTYPE_SEQUENCE, missing_ok); +} + /* * get_am_oid - given an access method name, look up its OID. * The type is not checked. @@ -215,6 +226,8 @@ get_am_type_string(char amtype) { case AMTYPE_INDEX: return "INDEX"; + case AMTYPE_SEQUENCE: + return "SEQUENCE"; case AMTYPE_TABLE: return "TABLE"; default: @@ -251,6 +264,9 @@ lookup_am_handler_func(List *handler_name, char amtype) case AMTYPE_INDEX: expectedType =3D INDEX_AM_HANDLEROID; break; + case AMTYPE_SEQUENCE: + expectedType =3D SEQUENCE_AM_HANDLEROID; + break; case AMTYPE_TABLE: expectedType =3D TABLE_AM_HANDLEROID; break; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequenc= e.c index d7a6523292..57db43501e 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -16,10 +16,10 @@ =20 #include "access/bufmask.h" #include "access/htup_details.h" -#include "access/localam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/sequence.h" +#include "access/sequenceam.h" #include "access/table.h" #include "access/transam.h" #include "access/xact.h" @@ -152,6 +152,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) stmt->inhRelations =3D NIL; stmt->constraints =3D NIL; stmt->options =3D NIL; + stmt->accessMethod =3D seq->accessMethod ? pstrdup(seq->accessMethod) : N= ULL; stmt->oncommit =3D ONCOMMIT_NOOP; stmt->tablespacename =3D NULL; stmt->if_not_exists =3D seq->if_not_exists; @@ -173,7 +174,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel =3D sequence_open(seqoid, AccessExclusiveLock); =20 /* now initialize the sequence table structure and its data */ - local_seq_init(rel, last_value, is_called); + sequence_init(rel, last_value, is_called); =20 /* process OWNED BY if given */ if (owned_by) @@ -241,7 +242,7 @@ ResetSequence(Oid seq_relid) ReleaseSysCache(pgstuple); =20 /* Sequence state is forcibly reset here. */ - local_seq_reset(seq_rel, startv, false, true); + sequence_reset(seq_rel, startv, false, true); =20 /* Clear local cache so that we don't think we have cached numbers */ /* Note that we do not change the currval() state */ @@ -297,7 +298,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) seqform =3D (Form_pg_sequence) GETSTRUCT(seqtuple); =20 /* Read sequence data */ - local_seq_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); =20 /* Check and set new values */ init_params(pstate, stmt->options, stmt->for_identity, false, @@ -310,7 +311,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); =20 - local_seq_reset(seqrel, last_value, is_called, reset_state); + sequence_reset(seqrel, last_value, is_called, reset_state); } =20 /* Clear local cache so that we don't think we have cached numbers */ @@ -346,7 +347,7 @@ SequenceChangePersistence(Oid relid, char newrelpersist= ence) if (RelationNeedsWAL(seqrel)) GetTopTransactionId(); =20 - local_seq_change_persistence(seqrel, newrelpersistence); + sequence_change_persistence(seqrel, newrelpersistence); =20 sequence_close(seqrel, NoLock); } @@ -463,8 +464,8 @@ nextval_internal(Oid relid, bool check_permissions) ReleaseSysCache(pgstuple); =20 /* retrieve next value from the access method */ - result =3D local_seq_nextval(seqrel, incby, maxv, minv, cache, cycle, - &last); + result =3D sequence_nextval(seqrel, incby, maxv, minv, cache, cycle, + &last); =20 /* save info in local cache */ elm->increment =3D incby; @@ -618,7 +619,7 @@ do_setval(Oid relid, int64 next, bool iscalled) GetTopTransactionId(); =20 /* Call the access method callback */ - local_seq_setval(seqrel, next, iscalled); + sequence_setval(seqrel, next, iscalled); =20 sequence_close(seqrel, NoLock); } @@ -1334,7 +1335,7 @@ pg_sequence_last_value(PG_FUNCTION_ARGS) errmsg("permission denied for sequence %s", RelationGetRelationName(seqrel)))); =20 - local_seq_get_state(seqrel, &last_value, &is_called); + sequence_get_state(seqrel, &last_value, &is_called); sequence_close(seqrel, NoLock); =20 values[0] =3D BoolGetDatum(is_called); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablec= mds.c index ea63412431..1ba58cec7d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -23,6 +23,7 @@ #include "access/reloptions.h" #include "access/relscan.h" #include "access/sysattr.h" +#include "access/sequenceam.h" #include "access/tableam.h" #include "access/toast_compression.h" #include "access/xact.h" @@ -963,14 +964,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ow= nerId, } =20 /* - * For relations with table AM and partitioned tables, select access - * method to use: an explicitly indicated one, or (in the case of a + * For relations with table AM, partitioned tables or sequences, select + * access method to use: an explicitly indicated one, or (in the case of a * partitioned table) the parent's, if it has one. */ if (stmt->accessMethod !=3D NULL) { - Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind =3D=3D RELKIND_PARTITION= ED_TABLE); - accessMethodId =3D get_table_am_oid(stmt->accessMethod, false); + Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind =3D=3D RELKIND_PARTITION= ED_TABLE || + RELKIND_HAS_SEQUENCE_AM(relkind)); + if (RELKIND_HAS_SEQUENCE_AM(relkind)) + accessMethodId =3D get_sequence_am_oid(stmt->accessMethod, false); + else + accessMethodId =3D get_table_am_oid(stmt->accessMethod, false); } else if (RELKIND_HAS_TABLE_AM(relkind) || relkind =3D=3D RELKIND_PARTITIO= NED_TABLE) { @@ -983,6 +988,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid own= erId, if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId)) accessMethodId =3D get_table_am_oid(default_table_access_method, false); } + else if (RELKIND_HAS_SEQUENCE_AM(relkind)) + { + accessMethodId =3D get_sequence_am_oid(default_sequence_access_method, f= alse); + } =20 /* * Create the relation. Inherited defaults and constraints are passed in diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile index 66bbad8e6e..86b1773c0b 100644 --- a/src/backend/nodes/Makefile +++ b/src/backend/nodes/Makefile @@ -47,6 +47,7 @@ node_headers =3D \ nodes/execnodes.h \ access/amapi.h \ access/sdir.h \ + access/sequenceam.h \ access/tableam.h \ access/tsmapi.h \ commands/event_trigger.h \ diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_= node_support.pl index 81df3bdf95..d2450e559d 100644 --- a/src/backend/nodes/gen_node_support.pl +++ b/src/backend/nodes/gen_node_support.pl @@ -59,6 +59,7 @@ my @all_input_files =3D qw( nodes/execnodes.h access/amapi.h access/sdir.h + access/sequenceam.h access/tableam.h access/tsmapi.h commands/event_trigger.h @@ -83,6 +84,7 @@ my @nodetag_only_files =3D qw( nodes/execnodes.h access/amapi.h access/sdir.h + access/sequenceam.h access/tableam.h access/tsmapi.h commands/event_trigger.h diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e8b619926e..2e33fb4578 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -391,6 +391,7 @@ static Node *makeRecursiveViewSelect(char *relname, Lis= t *aliases, Node *query); %type copy_file_name access_method_clause attr_name table_access_method_clause name cursor_name file_name + sequence_access_method_clause cluster_index_specification =20 %type func_name handler_name qual_Op qual_all_Op subquery_Op @@ -4929,23 +4930,26 @@ RefreshMatViewStmt: =20 CreateSeqStmt: CREATE OptTemp SEQUENCE qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n =3D makeNode(CreateSeqStmt); - $4->relpersistence =3D $2; n->sequence =3D $4; n->options =3D $5; + n->accessMethod =3D $6; n->ownerId =3D InvalidOid; n->if_not_exists =3D false; $$ =3D (Node *) n; } | CREATE OptTemp SEQUENCE IF_P NOT EXISTS qualified_name OptSeqOptList + sequence_access_method_clause { CreateSeqStmt *n =3D makeNode(CreateSeqStmt); =20 $7->relpersistence =3D $2; n->sequence =3D $7; n->options =3D $8; + n->accessMethod =3D $9; n->ownerId =3D InvalidOid; n->if_not_exists =3D true; $$ =3D (Node *) n; @@ -4982,6 +4986,11 @@ OptParenthesizedSeqOptList: '(' SeqOptList ')' { $$= =3D $2; } | /*EMPTY*/ { $$ =3D NIL; } ; =20 +sequence_access_method_clause: + USING name { $$ =3D $2; } + | /*EMPTY*/ { $$ =3D NULL; } + ; + SeqOptList: SeqOptElem { $$ =3D list_make1($1); } | SeqOptList SeqOptElem { $$ =3D lappend($1, $2); } ; @@ -5978,6 +5987,7 @@ CreateAmStmt: CREATE ACCESS METHOD name TYPE_P am_typ= e HANDLER handler_name =20 am_type: INDEX { $$ =3D AMTYPE_INDEX; } + | SEQUENCE { $$ =3D AMTYPE_SEQUENCE; } | TABLE { $$ =3D AMTYPE_TABLE; } ; =20 diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_= utilcmd.c index fef084f5d5..ae63168634 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -26,6 +26,7 @@ #include "access/htup_details.h" #include "access/relation.h" #include "access/reloptions.h" +#include "access/sequenceam.h" #include "access/table.h" #include "access/toast_compression.h" #include "catalog/dependency.h" @@ -473,6 +474,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, Column= Def *column, seqstmt->sequence->relpersistence =3D cxt->rel ? cxt->rel->rd_rel->relper= sistence : cxt->relation->relpersistence; =20 seqstmt->options =3D seqoptions; + seqstmt->accessMethod =3D NULL; =20 /* * If a sequence data type was specified, add it to the options. Prepend diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/ps= eudotypes.c index e189e9b79d..e31dbb0ebb 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -369,6 +369,7 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(language_handler); PSEUDOTYPE_DUMMY_IO_FUNCS(fdw_handler); PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler); PSEUDOTYPE_DUMMY_IO_FUNCS(index_am_handler); +PSEUDOTYPE_DUMMY_IO_FUNCS(sequence_am_handler); PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler); PSEUDOTYPE_DUMMY_IO_FUNCS(internal); PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/r= elcache.c index 262c9878dd..d9e40c5979 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -34,6 +34,7 @@ #include "access/multixact.h" #include "access/parallel.h" #include "access/reloptions.h" +#include "access/sequenceam.h" #include "access/sysattr.h" #include "access/table.h" #include "access/tableam.h" @@ -64,6 +65,7 @@ #include "catalog/pg_type.h" #include "catalog/schemapg.h" #include "catalog/storage.h" +#include "commands/defrem.h" #include "commands/policy.h" #include "commands/publicationcmds.h" #include "commands/trigger.h" @@ -300,6 +302,7 @@ static void RelationParseRelOptions(Relation relation, = HeapTuple tuple); static void RelationBuildTupleDesc(Relation relation); static Relation RelationBuildDesc(Oid targetRelId, bool insertIt); static void RelationInitPhysicalAddr(Relation relation); +static void RelationInitSequenceAccessMethod(Relation relation); static void load_critical_index(Oid indexoid, Oid heapoid); static TupleDesc GetPgClassDescriptor(void); static TupleDesc GetPgIndexDescriptor(void); @@ -1205,8 +1208,7 @@ retry: if (relation->rd_rel->relkind =3D=3D RELKIND_INDEX || relation->rd_rel->relkind =3D=3D RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); - else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || - relation->rd_rel->relkind =3D=3D RELKIND_SEQUENCE) + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) RelationInitTableAccessMethod(relation); else if (relation->rd_rel->relkind =3D=3D RELKIND_PARTITIONED_TABLE) { @@ -1215,6 +1217,8 @@ retry: * inherit. */ } + else if (RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind)) + RelationInitSequenceAccessMethod(relation); else Assert(relation->rd_rel->relam =3D=3D InvalidOid); =20 @@ -1811,17 +1815,9 @@ RelationInitTableAccessMethod(Relation relation) HeapTuple tuple; Form_pg_am aform; =20 - if (relation->rd_rel->relkind =3D=3D RELKIND_SEQUENCE) - { - /* - * Sequences are currently accessed like heap tables, but it doesn't - * seem prudent to show that in the catalog. So just overwrite it - * here. - */ - Assert(relation->rd_rel->relam =3D=3D InvalidOid); - relation->rd_amhandler =3D F_HEAP_TABLEAM_HANDLER; - } - else if (IsCatalogRelation(relation)) + Assert(RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)); + + if (IsCatalogRelation(relation)) { /* * Avoid doing a syscache lookup for catalog tables. @@ -1852,6 +1848,49 @@ RelationInitTableAccessMethod(Relation relation) InitTableAmRoutine(relation); } =20 +/* + * Initialize sequence-access-method support data for a sequence relation + */ +static void +RelationInitSequenceAccessMethod(Relation relation) +{ + HeapTuple tuple; + Form_pg_am aform; + const char *tableam_name; + Oid tableam_oid; + Oid tableam_handler; + + Assert(RELKIND_HAS_SEQUENCE_AM(relation->rd_rel->relkind)); + + /* + * Look up the sequence access method, save the OID of its handler + * function. + */ + Assert(relation->rd_rel->relam !=3D InvalidOid); + relation->rd_amhandler =3D GetSequenceAmRoutineId(relation->rd_rel->relam= ); + + /* + * Now we can fetch the sequence AM's API struct. + */ + relation->rd_sequenceam =3D GetSequenceAmRoutine(relation->rd_amhandler); + + /* + * From the sequence AM, set its expected table access method. + */ + tableam_name =3D sequence_get_table_am(relation); + tableam_oid =3D get_table_am_oid(tableam_name, false); + + tuple =3D SearchSysCache1(AMOID, ObjectIdGetDatum(tableam_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for access method %u", + tableam_oid); + aform =3D (Form_pg_am) GETSTRUCT(tuple); + tableam_handler =3D aform->amhandler; + ReleaseSysCache(tuple); + + relation->rd_tableam =3D GetTableAmRoutine(tableam_handler); +} + /* * formrdesc * @@ -3670,14 +3709,17 @@ RelationBuildLocalRelation(const char *relname, rel->rd_rel->relam =3D accessmtd; =20 /* - * RelationInitTableAccessMethod will do syscache lookups, so we mustn't - * run it in CacheMemoryContext. Fortunately, the remaining steps don't - * require a long-lived current context. + * RelationInitTableAccessMethod() and RelationInitSequenceAccessMethod() + * will do syscache lookups, so we mustn't run them in CacheMemoryContext. + * Fortunately, the remaining steps don't require a long-lived current + * context. */ MemoryContextSwitchTo(oldcxt); =20 - if (RELKIND_HAS_TABLE_AM(relkind) || relkind =3D=3D RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(relkind)) RelationInitTableAccessMethod(rel); + else if (relkind =3D=3D RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); =20 /* * Okay to insert into the relcache hash table. @@ -4292,13 +4334,21 @@ RelationCacheInitializePhase3(void) =20 /* Reload tableam data if needed */ if (relation->rd_tableam =3D=3D NULL && - (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || relation->rd_rel->r= elkind =3D=3D RELKIND_SEQUENCE)) + (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind))) { RelationInitTableAccessMethod(relation); Assert(relation->rd_tableam !=3D NULL); =20 restart =3D true; } + else if (relation->rd_sequenceam =3D=3D NULL && + relation->rd_rel->relkind =3D=3D RELKIND_SEQUENCE) + { + RelationInitSequenceAccessMethod(relation); + Assert(relation->rd_sequenceam !=3D NULL); + + restart =3D true; + } =20 /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -6319,8 +6369,10 @@ load_relcache_init_file(bool shared) nailed_rels++; =20 /* Load table AM data */ - if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || rel->rd_rel->relkind = =3D=3D RELKIND_SEQUENCE) + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) RelationInitTableAccessMethod(rel); + else if (rel->rd_rel->relkind =3D=3D RELKIND_SEQUENCE) + RelationInitSequenceAccessMethod(rel); =20 Assert(rel->rd_index =3D=3D NULL); Assert(rel->rd_indextuple =3D=3D NULL); @@ -6332,6 +6384,7 @@ load_relcache_init_file(bool shared) Assert(rel->rd_supportinfo =3D=3D NULL); Assert(rel->rd_indoption =3D=3D NULL); Assert(rel->rd_indcollation =3D=3D NULL); + Assert(rel->rd_sequenceam =3D=3D NULL); Assert(rel->rd_opcoptions =3D=3D NULL); } =20 diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/g= uc_tables.c index c68fdc008b..c384bc00ba 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -30,6 +30,7 @@ #include "access/gin.h" #include "access/slru.h" #include "access/toast_compression.h" +#include "access/sequenceam.h" #include "access/twophase.h" #include "access/xlog_internal.h" #include "access/xlogprefetcher.h" @@ -4139,6 +4140,17 @@ struct config_string ConfigureNamesString[] =3D check_default_table_access_method, NULL, NULL }, =20 + { + {"default_sequence_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Sets the default sequence access method for new sequences= =2E"), + NULL, + GUC_IS_NAME + }, + &default_sequence_access_method, + DEFAULT_SEQUENCE_ACCESS_METHOD, + check_default_sequence_access_method, NULL, NULL + }, + { {"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT, gettext_noop("Sets the default tablespace to create tables and indexes = in."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/ut= ils/misc/postgresql.conf.sample index 2166ea4a87..30d1662ce2 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -703,6 +703,7 @@ # error #search_path =3D '"$user", public' # schema names #row_security =3D on +#default_sequence_access_method =3D 'local' #default_table_access_method =3D 'heap' #default_tablespace =3D '' # a tablespace name, '' uses the default #default_toast_compression =3D 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/pg_waldump/t/001_basic.pl b/src/bin/pg_waldump/t/001_b= asic.pl index 082cb8a589..1dd822fe44 100644 --- a/src/bin/pg_waldump/t/001_basic.pl +++ b/src/bin/pg_waldump/t/001_basic.pl @@ -41,7 +41,7 @@ Btree Hash Gin Gist -Sequence +LocalSequence SPGist BRIN CommitTs diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 4a9ee4a54d..bad66978bb 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -161,10 +161,12 @@ describeAccessMethods(const char *pattern, bool verbo= se) "SELECT amname AS \"%s\",\n" " CASE amtype" " WHEN 'i' THEN '%s'" + " WHEN 's' THEN '%s'" " WHEN 't' THEN '%s'" " END AS \"%s\"", gettext_noop("Name"), gettext_noop("Index"), + gettext_noop("Sequence"), gettext_noop("Table"), gettext_noop("Type")); =20 diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 6fee3160f0..229dc203e3 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -2200,7 +2200,7 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "SEQUENCE", MatchAny)) COMPLETE_WITH("AS", "INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "START", "NO", "CACHE", "CYCLE", "SET", "OWNED BY", - "OWNER TO", "RENAME TO"); + "OWNER TO", "RENAME TO", "USING"); /* ALTER SEQUENCE AS */ else if (TailMatches("ALTER", "SEQUENCE", MatchAny, "AS")) COMPLETE_WITH_CS("smallint", "integer", "bigint"); @@ -3226,7 +3226,7 @@ psql_completion(const char *text, int start, int end) else if (TailMatches("CREATE", "SEQUENCE", MatchAny) || TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny)) COMPLETE_WITH("AS", "INCREMENT BY", "MINVALUE", "MAXVALUE", "NO", - "CACHE", "CYCLE", "OWNED BY", "START WITH"); + "CACHE", "CYCLE", "OWNED BY", "START WITH", "USING"); else if (TailMatches("CREATE", "SEQUENCE", MatchAny, "AS") || TailMatches("CREATE", "TEMP|TEMPORARY", "SEQUENCE", MatchAny, "AS")) COMPLETE_WITH_CS("smallint", "integer", "bigint"); diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/exp= ected/create_am.out index 9762c332ce..1e7ce970a8 100644 --- a/src/test/regress/expected/create_am.out +++ b/src/test/regress/expected/create_am.out @@ -163,11 +163,6 @@ CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT *= FROM tableam_tbl_heap2; ERROR: syntax error at or near "USING" LINE 1: CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM ... ^ --- CREATE SEQUENCE doesn't support USING -CREATE SEQUENCE tableam_seq_heap2 USING heap2; -ERROR: syntax error at or near "USING" -LINE 1: CREATE SEQUENCE tableam_seq_heap2 USING heap2; - ^ -- CREATE MATERIALIZED VIEW does support USING CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM = tableam_tbl_heap2; SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1; @@ -500,9 +495,12 @@ CREATE TABLE tableam_parted_heapx (a text, b int) PART= ITION BY list (a); CREATE TABLE tableam_parted_1_heapx PARTITION OF tableam_parted_heapx FOR = VALUES IN ('a', 'b'); -- but an explicitly set AM overrides it CREATE TABLE tableam_parted_2_heapx PARTITION OF tableam_parted_heapx FOR = VALUES IN ('c', 'd') USING heap; --- sequences, views and foreign servers shouldn't have an AM -CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx; +-- sequences have an AM +SET LOCAL default_sequence_access_method =3D 'local'; CREATE SEQUENCE tableam_seq_heapx; +RESET default_sequence_access_method; +-- views and foreign servers shouldn't have an AM +CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx; CREATE FOREIGN DATA WRAPPER fdw_heap2 VALIDATOR postgresql_fdw_validator; CREATE SERVER fs_heap2 FOREIGN DATA WRAPPER fdw_heap2 ; CREATE FOREIGN table tableam_fdw_heapx () SERVER fs_heap2; @@ -525,7 +523,7 @@ ORDER BY 3, 1, 2; r | heap2 | tableam_parted_1_heapx r | heap | tableam_parted_2_heapx p | | tableam_parted_heapx - S | | tableam_seq_heapx + S | local | tableam_seq_heapx r | heap2 | tableam_tbl_heapx r | heap2 | tableam_tblas_heapx m | heap2 | tableam_tblmv_heapx @@ -560,3 +558,22 @@ table tableam_parted_b_heap2 depends on access method = heap2 table tableam_parted_d_heap2 depends on access method heap2 HINT: Use DROP ... CASCADE to drop the dependent objects too. -- we intentionally leave the objects created above alive, to verify pg_du= mp support +-- Checks for sequence access methods +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); + nextval=20 +--------- + 1 +(1 row) + +-- Try to drop and fail on dependency +DROP ACCESS METHOD local2; +ERROR: cannot drop access method local2 because other objects depend on it +DETAIL: sequence test_seqam depends on access method local2 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- And cleanup +DROP SEQUENCE test_seqam; +DROP ACCESS METHOD local2; diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/ex= pected/opr_sanity.out index 9d047b21b8..ffb4c66853 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1931,6 +1931,18 @@ WHERE p1.oid =3D a1.amhandler AND a1.amtype =3D 't' = AND -----+--------+-----+--------- (0 rows) =20 +-- check for sequence amhandler functions with the wrong signature +SELECT a1.oid, a1.amname, p1.oid, p1.proname +FROM pg_am AS a1, pg_proc AS p1 +WHERE p1.oid =3D a1.amhandler AND a1.amtype =3D 's' AND + (p1.prorettype !=3D 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs !=3D 1 + OR p1.proargtypes[0] !=3D 'internal'::regtype); + oid | amname | oid | proname=20 +-----+--------+-----+--------- +(0 rows) + -- **************** pg_amop **************** -- Look for illegal values in pg_amop fields SELECT a1.amopfamily, a1.amopstrategy diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected= /psql.out index 3bbe4c5f97..52c6a360b6 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -4962,8 +4962,8 @@ Indexes: -- check printing info about access methods \dA List of access methods - Name | Type =20 ---------+------- + Name | Type =20 +--------+---------- brin | Index btree | Index gin | Index @@ -4971,13 +4971,14 @@ List of access methods hash | Index heap | Table heap2 | Table + local | Sequence spgist | Index -(8 rows) +(9 rows) =20 \dA * List of access methods - Name | Type =20 ---------+------- + Name | Type =20 +--------+---------- brin | Index btree | Index gin | Index @@ -4985,8 +4986,9 @@ List of access methods hash | Index heap | Table heap2 | Table + local | Sequence spgist | Index -(8 rows) +(9 rows) =20 \dA h* List of access methods @@ -5011,32 +5013,34 @@ List of access methods =20 \dA: extra argument "bar" ignored \dA+ - List of access methods - Name | Type | Handler | Description = =20 ---------+-------+----------------------+----------------------------------= ------ - brin | Index | brinhandler | block range index (BRIN) access m= ethod - btree | Index | bthandler | b-tree index access method - gin | Index | ginhandler | GIN index access method - gist | Index | gisthandler | GiST index access method - hash | Index | hashhandler | hash index access method - heap | Table | heap_tableam_handler | heap table access method - heap2 | Table | heap_tableam_handler |=20 - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description = =20 +--------+----------+--------------------------+---------------------------= ------------- + brin | Index | brinhandler | block range index (BRIN) a= ccess method + btree | Index | bthandler | b-tree index access method + gin | Index | ginhandler | GIN index access method + gist | Index | gisthandler | GiST index access method + hash | Index | hashhandler | hash index access method + heap | Table | heap_tableam_handler | heap table access method + heap2 | Table | heap_tableam_handler |=20 + local | Sequence | local_sequenceam_handler | local sequence access meth= od + spgist | Index | spghandler | SP-GiST index access method +(9 rows) =20 \dA+ * - List of access methods - Name | Type | Handler | Description = =20 ---------+-------+----------------------+----------------------------------= ------ - brin | Index | brinhandler | block range index (BRIN) access m= ethod - btree | Index | bthandler | b-tree index access method - gin | Index | ginhandler | GIN index access method - gist | Index | gisthandler | GiST index access method - hash | Index | hashhandler | hash index access method - heap | Table | heap_tableam_handler | heap table access method - heap2 | Table | heap_tableam_handler |=20 - spgist | Index | spghandler | SP-GiST index access method -(8 rows) + List of access methods + Name | Type | Handler | Description = =20 +--------+----------+--------------------------+---------------------------= ------------- + brin | Index | brinhandler | block range index (BRIN) a= ccess method + btree | Index | bthandler | b-tree index access method + gin | Index | ginhandler | GIN index access method + gist | Index | gisthandler | GiST index access method + hash | Index | hashhandler | hash index access method + heap | Table | heap_tableam_handler | heap table access method + heap2 | Table | heap_tableam_handler |=20 + local | Sequence | local_sequenceam_handler | local sequence access meth= od + spgist | Index | spghandler | SP-GiST index access method +(9 rows) =20 \dA+ h* List of access methods diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/crea= te_am.sql index 825aed325e..f928c3349c 100644 --- a/src/test/regress/sql/create_am.sql +++ b/src/test/regress/sql/create_am.sql @@ -117,9 +117,6 @@ SELECT INTO tableam_tblselectinto_heap2 USING heap2 FRO= M tableam_tbl_heap2; -- CREATE VIEW doesn't support USING CREATE VIEW tableam_view_heap2 USING heap2 AS SELECT * FROM tableam_tbl_he= ap2; =20 --- CREATE SEQUENCE doesn't support USING -CREATE SEQUENCE tableam_seq_heap2 USING heap2; - -- CREATE MATERIALIZED VIEW does support USING CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM = tableam_tbl_heap2; SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1; @@ -317,9 +314,13 @@ CREATE TABLE tableam_parted_1_heapx PARTITION OF table= am_parted_heapx FOR VALUES -- but an explicitly set AM overrides it CREATE TABLE tableam_parted_2_heapx PARTITION OF tableam_parted_heapx FOR = VALUES IN ('c', 'd') USING heap; =20 --- sequences, views and foreign servers shouldn't have an AM -CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx; +-- sequences have an AM +SET LOCAL default_sequence_access_method =3D 'local'; CREATE SEQUENCE tableam_seq_heapx; +RESET default_sequence_access_method; + +-- views and foreign servers shouldn't have an AM +CREATE VIEW tableam_view_heapx AS SELECT * FROM tableam_tbl_heapx; CREATE FOREIGN DATA WRAPPER fdw_heap2 VALIDATOR postgresql_fdw_validator; CREATE SERVER fs_heap2 FOREIGN DATA WRAPPER fdw_heap2 ; CREATE FOREIGN table tableam_fdw_heapx () SERVER fs_heap2; @@ -355,3 +356,16 @@ CREATE FOREIGN TABLE fp PARTITION OF tableam_parted_a_= heap2 DEFAULT SERVER x; DROP ACCESS METHOD heap2; =20 -- we intentionally leave the objects created above alive, to verify pg_du= mp support + +-- Checks for sequence access methods + +-- Create new sequence access method which uses standard local handler +CREATE ACCESS METHOD local2 TYPE SEQUENCE HANDLER local_sequenceam_handler; +-- Create and use sequence +CREATE SEQUENCE test_seqam USING local2; +SELECT nextval('test_seqam'::regclass); +-- Try to drop and fail on dependency +DROP ACCESS METHOD local2; +-- And cleanup +DROP SEQUENCE test_seqam; +DROP ACCESS METHOD local2; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr= _sanity.sql index 2fe7b6dcc4..1409622374 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -1229,6 +1229,16 @@ WHERE p1.oid =3D a1.amhandler AND a1.amtype =3D 't' = AND OR p1.pronargs !=3D 1 OR p1.proargtypes[0] !=3D 'internal'::regtype); =20 +-- check for sequence amhandler functions with the wrong signature + +SELECT a1.oid, a1.amname, p1.oid, p1.proname +FROM pg_am AS a1, pg_proc AS p1 +WHERE p1.oid =3D a1.amhandler AND a1.amtype =3D 's' AND + (p1.prorettype !=3D 'sequence_am_handler'::regtype + OR p1.proretset + OR p1.pronargs !=3D 1 + OR p1.proargtypes[0] !=3D 'internal'::regtype); + -- **************** pg_amop **************** =20 -- Look for illegal values in pg_amop fields diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs= =2Elist index d551ada325..f2364d6582 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2557,6 +2557,7 @@ SeqScan SeqScanState SeqTable SeqTableData +SequenceAmRoutine SerCommitSeqNo SerialControl SerialIOData @@ -3540,6 +3541,7 @@ lineno_t list_sort_comparator local_relopt local_relopts +local_sequence_magic local_source locale_t locate_agg_of_level_context @@ -3804,7 +3806,6 @@ save_buffer scram_state scram_state_enum sem_t -sequence_magic set_join_pathlist_hook_type set_rel_pathlist_hook_type shm_mq @@ -4019,6 +4020,7 @@ xl_heap_visible xl_invalid_page xl_invalid_page_key xl_invalidations +xl_local_seq_rec xl_logical_message xl_multi_insert_tuple xl_multixact_create @@ -4030,7 +4032,6 @@ xl_replorigin_drop xl_replorigin_set xl_restore_point xl_running_xacts -xl_seq_rec xl_smgr_create xl_smgr_truncate xl_standby_lock --=20 2.43.0 --OKPlUpZv0l0WYI+j Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0006-Sequence-access-methods-dump-restore-support.patch" Content-Transfer-Encoding: quoted-printable =46rom efdcdf9b0f9ee85db6de6146b55884db4128945f Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Fri, 1 Dec 2023 12:56:26 +0900 Subject: [PATCH v4 6/8] Sequence access methods - dump/restore support --- src/bin/pg_dump/pg_backup.h | 2 + src/bin/pg_dump/pg_backup_archiver.c | 66 ++++++++++++++++++++++++++++ src/bin/pg_dump/pg_backup_archiver.h | 6 ++- src/bin/pg_dump/pg_dump.c | 39 ++++++++++++++-- src/bin/pg_dump/pg_dumpall.c | 5 +++ src/bin/pg_dump/pg_restore.c | 4 ++ src/bin/pg_dump/t/002_pg_dump.pl | 63 ++++++++++++++++++++++---- doc/src/sgml/ref/pg_dump.sgml | 17 +++++++ doc/src/sgml/ref/pg_dumpall.sgml | 11 +++++ doc/src/sgml/ref/pg_restore.sgml | 11 +++++ 10 files changed, 210 insertions(+), 14 deletions(-) diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index fbf5f1c515..4eae73c16e 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -95,6 +95,7 @@ typedef struct _restoreOptions { int createDB; /* Issue commands to create the database */ int noOwner; /* Don't try to match original object owner */ + int noSequenceAm; /* Don't issue sequence-AM-related commands */ int noTableAm; /* Don't issue table-AM-related commands */ int noTablespace; /* Don't issue tablespace-related commands */ int disable_triggers; /* disable triggers during data-only @@ -185,6 +186,7 @@ typedef struct _dumpOptions int no_unlogged_table_data; int serializable_deferrable; int disable_triggers; + int outputNoSequenceAm; int outputNoTableAm; int outputNoTablespaces; int use_setsessauth; diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_back= up_archiver.c index c7a6c918a6..fbc7e49f36 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -171,6 +171,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputSuperuser =3D ropt->superuser; dopt->outputCreateDB =3D ropt->createDB; dopt->outputNoOwner =3D ropt->noOwner; + dopt->outputNoSequenceAm =3D ropt->noSequenceAm; dopt->outputNoTableAm =3D ropt->noTableAm; dopt->outputNoTablespaces =3D ropt->noTablespace; dopt->disable_triggers =3D ropt->disable_triggers; @@ -1221,6 +1222,7 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpI= d dumpId, newToc->tag =3D pg_strdup(opts->tag); newToc->namespace =3D opts->namespace ? pg_strdup(opts->namespace) : NULL; newToc->tablespace =3D opts->tablespace ? pg_strdup(opts->tablespace) : N= ULL; + newToc->sequenceam =3D opts->sequenceam ? pg_strdup(opts->sequenceam) : N= ULL; newToc->tableam =3D opts->tableam ? pg_strdup(opts->tableam) : NULL; newToc->owner =3D opts->owner ? pg_strdup(opts->owner) : NULL; newToc->desc =3D pg_strdup(opts->description); @@ -2372,6 +2374,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fm= t, =20 AH->currUser =3D NULL; /* unknown */ AH->currSchema =3D NULL; /* ditto */ + AH->currSequenceAm =3D NULL; /* ditto */ AH->currTablespace =3D NULL; /* ditto */ AH->currTableAm =3D NULL; /* ditto */ =20 @@ -2601,6 +2604,7 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); WriteStr(AH, te->tablespace); + WriteStr(AH, te->sequenceam); WriteStr(AH, te->tableam); WriteStr(AH, te->owner); WriteStr(AH, "false"); @@ -2704,6 +2708,9 @@ ReadToc(ArchiveHandle *AH) if (AH->version >=3D K_VERS_1_10) te->tablespace =3D ReadStr(AH); =20 + if (AH->version >=3D K_VERS_1_17) + te->sequenceam =3D ReadStr(AH); + if (AH->version >=3D K_VERS_1_14) te->tableam =3D ReadStr(AH); =20 @@ -3356,6 +3363,9 @@ _reconnectToDB(ArchiveHandle *AH, const char *dbname) free(AH->currSchema); AH->currSchema =3D NULL; =20 + free(AH->currSequenceAm); + AH->currSequenceAm =3D NULL; + free(AH->currTableAm); AH->currTableAm =3D NULL; =20 @@ -3518,6 +3528,57 @@ _selectTablespace(ArchiveHandle *AH, const char *tab= lespace) destroyPQExpBuffer(qry); } =20 +/* + * Set the proper default_sequence_access_method value for the sequence. + */ +static void +_selectSequenceAccessMethod(ArchiveHandle *AH, const char *sequenceam) +{ + RestoreOptions *ropt =3D AH->public.ropt; + PQExpBuffer cmd; + const char *want, + *have; + + /* do nothing in --no-sequence-access-method mode */ + if (ropt->noSequenceAm) + return; + + have =3D AH->currSequenceAm; + want =3D sequenceam; + + if (!want) + return; + + if (have && strcmp(want, have) =3D=3D 0) + return; + + cmd =3D createPQExpBuffer(); + appendPQExpBuffer(cmd, + "SET default_sequence_access_method =3D %s;", + fmtId(want)); + + if (RestoringToDB(AH)) + { + PGresult *res; + + res =3D PQexec(AH->connection, cmd->data); + + if (!res || PQresultStatus(res) !=3D PGRES_COMMAND_OK) + warn_or_exit_horribly(AH, + "could not set default_sequence_access_method: %s", + PQerrorMessage(AH->connection)); + + PQclear(res); + } + else + ahprintf(AH, "%s\n\n", cmd->data); + + destroyPQExpBuffer(cmd); + + free(AH->currSequenceAm); + AH->currSequenceAm =3D pg_strdup(want); +} + /* * Set the proper default_table_access_method value for the table. */ @@ -3677,6 +3738,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool = isData) _becomeOwner(AH, te); _selectOutputSchema(AH, te->namespace); _selectTablespace(AH, te->tablespace); + _selectSequenceAccessMethod(AH, te->sequenceam); _selectTableAccessMethod(AH, te->tableam); =20 /* Emit header comment for item */ @@ -4171,6 +4233,8 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEnt= ry *pending_list) AH->currUser =3D NULL; free(AH->currSchema); AH->currSchema =3D NULL; + free(AH->currSequenceAm); + AH->currSequenceAm =3D NULL; free(AH->currTablespace); AH->currTablespace =3D NULL; free(AH->currTableAm); @@ -4910,6 +4974,7 @@ CloneArchive(ArchiveHandle *AH) clone->connCancel =3D NULL; clone->currUser =3D NULL; clone->currSchema =3D NULL; + clone->currSequenceAm =3D NULL; clone->currTableAm =3D NULL; clone->currTablespace =3D NULL; =20 @@ -4969,6 +5034,7 @@ DeCloneArchive(ArchiveHandle *AH) /* Clear any connection-local state */ free(AH->currUser); free(AH->currSchema); + free(AH->currSequenceAm); free(AH->currTablespace); free(AH->currTableAm); free(AH->savedPassword); diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_back= up_archiver.h index d6104a7196..9eb6f45389 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -70,10 +70,11 @@ * in header */ #define K_VERS_1_16 MAKE_ARCHIVE_VERSION(1, 16, 0) /* BLOB METADATA entries * and multiple BLOBS */ +#define K_VERS_1_17 MAKE_ARCHIVE_VERSION(1, 17, 0) /* add sequenceam */ =20 /* Current archive version number (the format we can output) */ #define K_VERS_MAJOR 1 -#define K_VERS_MINOR 16 +#define K_VERS_MINOR 17 #define K_VERS_REV 0 #define K_VERS_SELF MAKE_ARCHIVE_VERSION(K_VERS_MAJOR, K_VERS_MINOR, K_VER= S_REV) =20 @@ -321,6 +322,7 @@ struct _archiveHandle /* these vars track state to avoid sending redundant SET commands */ char *currUser; /* current username, or NULL if unknown */ char *currSchema; /* current schema, or NULL */ + char *currSequenceAm; /* current sequence access method, or NULL */ char *currTablespace; /* current tablespace, or NULL */ char *currTableAm; /* current table access method, or NULL */ =20 @@ -352,6 +354,7 @@ struct _tocEntry char *namespace; /* null or empty string if not in a schema */ char *tablespace; /* null if not in a tablespace; empty string * means use database default */ + char *sequenceam; /* table access method, only for SEQUENCE tags */ char *tableam; /* table access method, only for TABLE tags */ char *owner; char *desc; @@ -392,6 +395,7 @@ typedef struct _archiveOpts const char *tag; const char *namespace; const char *tablespace; + const char *sequenceam; const char *tableam; const char *owner; const char *description; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 851d228f79..83d0cb2165 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -422,6 +422,7 @@ main(int argc, char **argv) {"if-exists", no_argument, &dopt.if_exists, 1}, {"inserts", no_argument, NULL, 9}, {"lock-wait-timeout", required_argument, NULL, 2}, + {"no-sequence-access-method", no_argument, &dopt.outputNoSequenceAm, 1}, {"no-table-access-method", no_argument, &dopt.outputNoTableAm, 1}, {"no-tablespaces", no_argument, &dopt.outputNoTablespaces, 1}, {"quote-all-identifiers", no_argument, "e_all_identifiers, 1}, @@ -1036,6 +1037,7 @@ main(int argc, char **argv) ropt->superuser =3D dopt.outputSuperuser; ropt->createDB =3D dopt.outputCreateDB; ropt->noOwner =3D dopt.outputNoOwner; + ropt->noSequenceAm =3D dopt.outputNoSequenceAm; ropt->noTableAm =3D dopt.outputNoTableAm; ropt->noTablespace =3D dopt.outputNoTablespaces; ropt->disable_triggers =3D dopt.disable_triggers; @@ -1152,6 +1154,7 @@ help(const char *progname) printf(_(" --no-publications do not dump publications\n")); printf(_(" --no-security-labels do not dump security label assig= nments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sequence-access-method do not sequence table access met= hods\n")); printf(_(" --no-table-access-method do not dump table access methods= \n")); printf(_(" --no-tablespaces do not dump tablespace assignmen= ts\n")); printf(_(" --no-toast-compression do not dump TOAST compression me= thods\n")); @@ -13306,6 +13309,9 @@ dumpAccessMethod(Archive *fout, const AccessMethodI= nfo *aminfo) case AMTYPE_INDEX: appendPQExpBufferStr(q, "TYPE INDEX "); break; + case AMTYPE_SEQUENCE: + appendPQExpBufferStr(q, "TYPE SEQUENCE "); + break; case AMTYPE_TABLE: appendPQExpBufferStr(q, "TYPE TABLE "); break; @@ -17547,7 +17553,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) *maxv, *minv, *cache, - *seqtype; + *seqtype, + *seqam; bool cycled; bool is_ascending; int64 default_minv, @@ -17561,13 +17568,35 @@ dumpSequence(Archive *fout, const TableInfo *tbin= fo) =20 qseqname =3D pg_strdup(fmtId(tbinfo->dobj.name)); =20 - if (fout->remoteVersion >=3D 100000) + if (fout->remoteVersion >=3D 170000) { + /* + * PostgreSQL 17 has added support for sequence access methods. + */ + appendPQExpBuffer(query, + "SELECT format_type(s.seqtypid, NULL), " + "s.seqstart, s.seqincrement, " + "s.seqmax, s.seqmin, " + "s.seqcache, s.seqcycle, " + "a.amname AS seqam " + "FROM pg_catalog.pg_sequence s " + "JOIN pg_class c ON (c.oid =3D s.seqrelid) " + "JOIN pg_am a ON (a.oid =3D c.relam) " + "WHERE s.seqrelid =3D '%u'::oid", + tbinfo->dobj.catId.oid); + } + else if (fout->remoteVersion >=3D 100000) + { + /* + * PostgreSQL 10 has moved sequence metadata to the catalog + * pg_sequence. + */ appendPQExpBuffer(query, "SELECT format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " - "seqcache, seqcycle " + "seqcache, seqcycle, " + "'local' AS seqam " "FROM pg_catalog.pg_sequence " "WHERE seqrelid =3D '%u'::oid", tbinfo->dobj.catId.oid); @@ -17583,7 +17612,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) appendPQExpBuffer(query, "SELECT 'bigint' AS sequence_type, " "start_value, increment_by, max_value, min_value, " - "cache_value, is_cycled FROM %s", + "cache_value, is_cycled, 'local' as seqam FROM %s", fmtQualifiedDumpable(tbinfo)); } =20 @@ -17602,6 +17631,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) minv =3D PQgetvalue(res, 0, 4); cache =3D PQgetvalue(res, 0, 5); cycled =3D (strcmp(PQgetvalue(res, 0, 6), "t") =3D=3D 0); + seqam =3D PQgetvalue(res, 0, 7); =20 /* Calculate default limits for a sequence of this type */ is_ascending =3D (incby[0] !=3D '-'); @@ -17733,6 +17763,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) ARCHIVE_OPTS(.tag =3D tbinfo->dobj.name, .namespace =3D tbinfo->dobj.namespace->dobj.name, .owner =3D tbinfo->rolname, + .sequenceam =3D seqam, .description =3D "SEQUENCE", .section =3D SECTION_PRE_DATA, .createStmt =3D query->data, diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 73337f3392..756a63c24d 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -97,6 +97,7 @@ static int disable_dollar_quoting =3D 0; static int disable_triggers =3D 0; static int if_exists =3D 0; static int inserts =3D 0; +static int no_sequence_access_method =3D 0; static int no_table_access_method =3D 0; static int no_tablespaces =3D 0; static int use_setsessauth =3D 0; @@ -161,6 +162,7 @@ main(int argc, char *argv[]) {"if-exists", no_argument, &if_exists, 1}, {"inserts", no_argument, &inserts, 1}, {"lock-wait-timeout", required_argument, NULL, 2}, + {"no-sequence-access-method", no_argument, &no_sequence_access_method, 1= }, {"no-table-access-method", no_argument, &no_table_access_method, 1}, {"no-tablespaces", no_argument, &no_tablespaces, 1}, {"quote-all-identifiers", no_argument, "e_all_identifiers, 1}, @@ -435,6 +437,8 @@ main(int argc, char *argv[]) appendPQExpBufferStr(pgdumpopts, " --disable-triggers"); if (inserts) appendPQExpBufferStr(pgdumpopts, " --inserts"); + if (no_sequence_access_method) + appendPQExpBufferStr(pgdumpopts, " --no-sequence-access-method"); if (no_table_access_method) appendPQExpBufferStr(pgdumpopts, " --no-table-access-method"); if (no_tablespaces) @@ -668,6 +672,7 @@ help(void) printf(_(" --no-security-labels do not dump security label assig= nments\n")); printf(_(" --no-subscriptions do not dump subscriptions\n")); printf(_(" --no-sync do not wait for changes to be wr= itten safely to disk\n")); + printf(_(" --no-sequence-access-method do not dump sequence access meth= ods\n")); printf(_(" --no-table-access-method do not dump table access methods= \n")); printf(_(" --no-tablespaces do not dump tablespace assignmen= ts\n")); printf(_(" --no-toast-compression do not dump TOAST compression me= thods\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index 5ea78cf7cc..077489f4b1 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -68,6 +68,7 @@ main(int argc, char **argv) static int enable_row_security =3D 0; static int if_exists =3D 0; static int no_data_for_failed_tables =3D 0; + static int outputNoSequenceAm =3D 0; static int outputNoTableAm =3D 0; static int outputNoTablespaces =3D 0; static int use_setsessauth =3D 0; @@ -115,6 +116,7 @@ main(int argc, char **argv) {"enable-row-security", no_argument, &enable_row_security, 1}, {"if-exists", no_argument, &if_exists, 1}, {"no-data-for-failed-tables", no_argument, &no_data_for_failed_tables, 1= }, + {"no-sequence-access-method", no_argument, &outputNoSequenceAm, 1}, {"no-table-access-method", no_argument, &outputNoTableAm, 1}, {"no-tablespaces", no_argument, &outputNoTablespaces, 1}, {"role", required_argument, NULL, 2}, @@ -363,6 +365,7 @@ main(int argc, char **argv) opts->disable_triggers =3D disable_triggers; opts->enable_row_security =3D enable_row_security; opts->noDataForFailedTables =3D no_data_for_failed_tables; + opts->noSequenceAm =3D outputNoSequenceAm; opts->noTableAm =3D outputNoTableAm; opts->noTablespace =3D outputNoTablespaces; opts->use_setsessauth =3D use_setsessauth; @@ -491,6 +494,7 @@ usage(const char *progname) printf(_(" --no-publications do not restore publications\n")); printf(_(" --no-security-labels do not restore security labels\n= ")); printf(_(" --no-subscriptions do not restore subscriptions\n")= ); + printf(_(" --no-sequence-access-method do not restore sequence acces= s methods\n")); printf(_(" --no-table-access-method do not restore table access meth= ods\n")); printf(_(" --no-tablespaces do not restore tablespace assign= ments\n")); printf(_(" --section=3DSECTION restore named section (pre-dat= a, data, or post-data)\n")); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_du= mp.pl index 0c057fef94..690501a189 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -537,6 +537,13 @@ my %pgdump_runs =3D ( 'postgres', ], }, + no_sequence_access_method =3D> { + dump_cmd =3D> [ + 'pg_dump', '--no-sync', + "--file=3D$tempdir/no_sequence_access_method.sql", + '--no-sequence-access-method', 'postgres', + ], + }, no_table_access_method =3D> { dump_cmd =3D> [ 'pg_dump', '--no-sync', @@ -711,6 +718,7 @@ my %full_runs =3D ( no_large_objects =3D> 1, no_owner =3D> 1, no_privs =3D> 1, + no_sequence_access_method =3D> 1, no_table_access_method =3D> 1, pg_dumpall_dbprivs =3D> 1, pg_dumpall_exclude =3D> 1, @@ -3855,9 +3863,7 @@ my %tests =3D ( \QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measure= ment USING\E /xm, like =3D> { - %full_runs, - %dump_test_schema_runs, - section_post_data =3D> 1, + %full_runs, %dump_test_schema_runs, section_post_data =3D> 1, }, unlike =3D> { exclude_dump_test_schema =3D> 1, @@ -4526,6 +4532,18 @@ my %tests =3D ( }, }, =20 + 'CREATE ACCESS METHOD regress_test_sequence_am' =3D> { + create_order =3D> 11, + create_sql =3D> + 'CREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER local_= sequenceam_handler;', + regexp =3D> qr/^ + \QCREATE ACCESS METHOD regress_sequence_am TYPE SEQUENCE HANDLER local_= sequenceam_handler;\E + \n/xm, + like =3D> { + %full_runs, section_pre_data =3D> 1, + }, + }, + # It's a bit tricky to ensure that the proper SET of default table # AM occurs. To achieve that we create a table with the standard # AM, test AM, standard AM. That guarantees that there needs to be @@ -4554,6 +4572,35 @@ my %tests =3D ( }, }, =20 + + # This uses the same trick as for materialized views and tables, + # but this time with a sequence access method, checking that a + # correct set of SET queries are created. + 'CREATE SEQUENCE regress_pg_dump_seq_am' =3D> { + create_order =3D> 12, + create_sql =3D> ' + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_0 USING local; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1 USING regress_sequen= ce_am; + CREATE SEQUENCE dump_test.regress_pg_dump_seq_am_2 USING local;', + regexp =3D> qr/^ + \QSET default_sequence_access_method =3D regress_sequence_am;\E + (\n(?!SET[^;]+;)[^\n]*)* + \n\QCREATE SEQUENCE dump_test.regress_pg_dump_seq_am_1\E + \n\s+\QSTART WITH 1\E + \n\s+\QINCREMENT BY 1\E + \n\s+\QNO MINVALUE\E + \n\s+\QNO MAXVALUE\E + \n\s+\QCACHE 1;\E\n/xm, + like =3D> { + %full_runs, %dump_test_schema_runs, section_pre_data =3D> 1, + }, + unlike =3D> { + exclude_dump_test_schema =3D> 1, + no_sequence_access_method =3D> 1, + only_dump_measurement =3D> 1, + }, + }, + 'CREATE MATERIALIZED VIEW regress_pg_dump_matview_am' =3D> { create_order =3D> 13, create_sql =3D> ' @@ -4783,10 +4830,8 @@ $node->command_fails_like( ############################################################## # Test dumping pg_catalog (for research -- cannot be reloaded) =20 -$node->command_ok( - [ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ], - 'pg_dump: option -n pg_catalog' -); +$node->command_ok([ 'pg_dump', '-p', "$port", '-n', 'pg_catalog' ], + 'pg_dump: option -n pg_catalog'); =20 ######################################### # Test valid database exclusion patterns @@ -4953,8 +4998,8 @@ foreach my $run (sort keys %pgdump_runs) } # Check for useless entries in "unlike" list. Runs that are # not listed in "like" don't need to be excluded in "unlike". - if ($tests{$test}->{unlike}->{$test_key} && - !defined($tests{$test}->{like}->{$test_key})) + if ($tests{$test}->{unlike}->{$test_key} + && !defined($tests{$test}->{like}->{$test_key})) { die "useless \"unlike\" entry \"$test_key\" in test \"$test\""; } diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index b99793e414..bb70d3a40f 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1104,6 +1104,23 @@ PostgreSQL documentation =20 + + + + + Do not output commands to select sequence access methods. + With this option, all objects will be created with whichever + sequence access method is the default during restore. + + + + This option is ignored when emitting an archive (non-text) output + file. For the archive formats, you can specify the option when you + call pg_restore. + + + + diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall= =2Esgml index 4d7c046468..34643175fb 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -479,6 +479,17 @@ exclude database PATT= ERN =20 + + + + + Do not output commands to select sequence access methods. + With this option, all objects will be created with whichever + sequence access method is the default during restore. + + + + diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore= =2Esgml index 2e3ba80258..c552a1e746 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -733,6 +733,17 @@ PostgreSQL documentation =20 + + + + + Do not output commands to select sequence access methods. + With this option, all objects will be created with whichever + sequence access method is the default during restore. + + + + --=20 2.43.0 --OKPlUpZv0l0WYI+j Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0007-dummy_sequence_am-Example-of-sequence-AM.patch" Content-Transfer-Encoding: quoted-printable =46rom b7876b4616463f3d2d18eabd8eb5ab33fbb85681 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Fri, 1 Dec 2023 12:56:52 +0900 Subject: [PATCH v4 7/8] dummy_sequence_am: Example of sequence AM --- src/test/modules/Makefile | 1 + src/test/modules/dummy_sequence_am/.gitignore | 3 + src/test/modules/dummy_sequence_am/Makefile | 19 +++ .../dummy_sequence_am--1.0.sql | 13 +++ .../dummy_sequence_am/dummy_sequence_am.c | 110 ++++++++++++++++++ .../dummy_sequence_am.control | 5 + .../expected/dummy_sequence.out | 35 ++++++ .../modules/dummy_sequence_am/meson.build | 33 ++++++ .../dummy_sequence_am/sql/dummy_sequence.sql | 17 +++ src/test/modules/meson.build | 1 + 10 files changed, 237 insertions(+) create mode 100644 src/test/modules/dummy_sequence_am/.gitignore create mode 100644 src/test/modules/dummy_sequence_am/Makefile create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am--1= =2E0.sql create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.c create mode 100644 src/test/modules/dummy_sequence_am/dummy_sequence_am.co= ntrol create mode 100644 src/test/modules/dummy_sequence_am/expected/dummy_seque= nce.out create mode 100644 src/test/modules/dummy_sequence_am/meson.build create mode 100644 src/test/modules/dummy_sequence_am/sql/dummy_sequence.s= ql diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 256799f520..caeb7a6033 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -10,6 +10,7 @@ SUBDIRS =3D \ delay_execution \ dummy_index_am \ dummy_seclabel \ + dummy_sequence_am \ libpq_pipeline \ plsample \ spgist_name_ops \ diff --git a/src/test/modules/dummy_sequence_am/.gitignore b/src/test/modul= es/dummy_sequence_am/.gitignore new file mode 100644 index 0000000000..44d119cfcc --- /dev/null +++ b/src/test/modules/dummy_sequence_am/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/src/test/modules/dummy_sequence_am/Makefile b/src/test/modules= /dummy_sequence_am/Makefile new file mode 100644 index 0000000000..391f7ac946 --- /dev/null +++ b/src/test/modules/dummy_sequence_am/Makefile @@ -0,0 +1,19 @@ +# src/test/modules/dummy_sequence_am/Makefile + +MODULES =3D dummy_sequence_am + +EXTENSION =3D dummy_sequence_am +DATA =3D dummy_sequence_am--1.0.sql + +REGRESS =3D dummy_sequence + +ifdef USE_PGXS +PG_CONFIG =3D pg_config +PGXS :=3D $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir =3D src/test/modules/dummy_sequence_am +top_builddir =3D ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql = b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql new file mode 100644 index 0000000000..e12b1f9d87 --- /dev/null +++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql @@ -0,0 +1,13 @@ +/* src/test/modules/dummy_sequence_am/dummy_sequence_am--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION dummy_sequence_am" to load this file. \quit + +CREATE FUNCTION dummy_sequenceam_handler(internal) + RETURNS sequence_am_handler + AS 'MODULE_PATHNAME' + LANGUAGE C; + +CREATE ACCESS METHOD dummy_sequence_am + TYPE SEQUENCE HANDLER dummy_sequenceam_handler; +COMMENT ON ACCESS METHOD dummy_sequence_am IS 'dummy sequence access metho= d'; diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.c b/src/t= est/modules/dummy_sequence_am/dummy_sequence_am.c new file mode 100644 index 0000000000..b5ee5d89da --- /dev/null +++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.c @@ -0,0 +1,110 @@ +/*------------------------------------------------------------------------- + * + * dummy_sequence_am.c + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/test/modules/dummy_sequence_am/dummy_sequence_am.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/sequenceam.h" +#include "fmgr.h" + +PG_MODULE_MAGIC; + +/* this sequence is fully on-memory */ +static int dummy_seqam_last_value =3D 1; +static bool dummy_seqam_is_called =3D false; + +PG_FUNCTION_INFO_V1(dummy_sequenceam_handler); + + +/* ------------------------------------------------------------------------ + * Callbacks for the dummy sequence access method. + * ------------------------------------------------------------------------ + */ + +/* + * Return the table access method used by this sequence. + * + * This is just an on-memory sequence, so anything is fine. + */ +static const char * +dummy_sequenceam_get_table_am(void) +{ + return "heap"; +} + +static void +dummy_sequenceam_init(Relation rel, int64 last_value, bool is_called) +{ + dummy_seqam_last_value =3D last_value; + dummy_seqam_is_called =3D is_called; +} + +static int64 +dummy_sequenceam_nextval(Relation rel, int64 incby, int64 maxv, + int64 minv, int64 cache, bool cycle, + int64 *last) +{ + dummy_seqam_last_value +=3D incby; + dummy_seqam_is_called =3D true; + + return dummy_seqam_last_value; +} + +static void +dummy_sequenceam_setval(Relation rel, int64 next, bool iscalled) +{ + dummy_seqam_last_value =3D next; + dummy_seqam_is_called =3D iscalled; +} + +static void +dummy_sequenceam_get_state(Relation rel, int64 *last_value, bool *is_calle= d) +{ + *last_value =3D dummy_seqam_last_value; + *is_called =3D dummy_seqam_is_called; +} + +static void +dummy_sequenceam_reset(Relation rel, int64 startv, bool is_called, + bool reset_state) +{ + dummy_seqam_last_value =3D startv; + dummy_seqam_is_called =3D is_called; +} + +static void +dummy_sequenceam_change_persistence(Relation rel, char newrelpersistence) +{ + /* nothing to do, really */ +} + +/* ------------------------------------------------------------------------ + * Definition of the dummy sequence access method. + * ------------------------------------------------------------------------ + */ + +static const SequenceAmRoutine dummy_sequenceam_methods =3D { + .type =3D T_SequenceAmRoutine, + .get_table_am =3D dummy_sequenceam_get_table_am, + .init =3D dummy_sequenceam_init, + .nextval =3D dummy_sequenceam_nextval, + .setval =3D dummy_sequenceam_setval, + .get_state =3D dummy_sequenceam_get_state, + .reset =3D dummy_sequenceam_reset, + .change_persistence =3D dummy_sequenceam_change_persistence +}; + +Datum +dummy_sequenceam_handler(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(&dummy_sequenceam_methods); +} diff --git a/src/test/modules/dummy_sequence_am/dummy_sequence_am.control b= /src/test/modules/dummy_sequence_am/dummy_sequence_am.control new file mode 100644 index 0000000000..9f10622f2f --- /dev/null +++ b/src/test/modules/dummy_sequence_am/dummy_sequence_am.control @@ -0,0 +1,5 @@ +# dummy_sequence_am extension +comment =3D 'dummy_sequence_am - sequence access method template' +default_version =3D '1.0' +module_pathname =3D '$libdir/dummy_sequence_am' +relocatable =3D true diff --git a/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out= b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out new file mode 100644 index 0000000000..57588cea5b --- /dev/null +++ b/src/test/modules/dummy_sequence_am/expected/dummy_sequence.out @@ -0,0 +1,35 @@ +CREATE EXTENSION dummy_sequence_am; +CREATE SEQUENCE dummyseq USING dummy_sequence_am; +SELECT nextval('dummyseq'::regclass); + nextval=20 +--------- + 2 +(1 row) + +SELECT setval('dummyseq'::regclass, 14); + setval=20 +-------- + 14 +(1 row) + +SELECT nextval('dummyseq'::regclass); + nextval=20 +--------- + 15 +(1 row) + +-- Sequence relation exists, but it has no attributes. +SELECT * FROM dummyseq; +-- +(0 rows) + +-- Reset connection, which will reset the sequence +\c +SELECT nextval('dummyseq'::regclass); + nextval=20 +--------- + 2 +(1 row) + +DROP SEQUENCE dummyseq; +DROP EXTENSION dummy_sequence_am; diff --git a/src/test/modules/dummy_sequence_am/meson.build b/src/test/modu= les/dummy_sequence_am/meson.build new file mode 100644 index 0000000000..84460070e4 --- /dev/null +++ b/src/test/modules/dummy_sequence_am/meson.build @@ -0,0 +1,33 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +dummy_sequence_am_sources =3D files( + 'dummy_sequence_am.c', +) + +if host_system =3D=3D 'windows' + dummy_sequence_am_sources +=3D rc_lib_gen.process(win32ver_rc, extra_arg= s: [ + '--NAME', 'dummy_sequence_am', + '--FILEDESC', 'dummy_sequence_am - sequence access method template',]) +endif + +dummy_sequence_am =3D shared_module('dummy_sequence_am', + dummy_sequence_am_sources, + kwargs: pg_test_mod_args, +) +test_install_libs +=3D dummy_sequence_am + +test_install_data +=3D files( + 'dummy_sequence_am.control', + 'dummy_sequence_am--1.0.sql', +) + +tests +=3D { + 'name': 'dummy_sequence_am', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'dummy_sequence', + ], + }, +} diff --git a/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql b/sr= c/test/modules/dummy_sequence_am/sql/dummy_sequence.sql new file mode 100644 index 0000000000..c739b29a46 --- /dev/null +++ b/src/test/modules/dummy_sequence_am/sql/dummy_sequence.sql @@ -0,0 +1,17 @@ +CREATE EXTENSION dummy_sequence_am; + +CREATE SEQUENCE dummyseq USING dummy_sequence_am; + +SELECT nextval('dummyseq'::regclass); +SELECT setval('dummyseq'::regclass, 14); +SELECT nextval('dummyseq'::regclass); + +-- Sequence relation exists, but it has no attributes. +SELECT * FROM dummyseq; + +-- Reset connection, which will reset the sequence +\c +SELECT nextval('dummyseq'::regclass); + +DROP SEQUENCE dummyseq; +DROP EXTENSION dummy_sequence_am; diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index d8fe059d23..c977bc36b5 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -5,6 +5,7 @@ subdir('commit_ts') subdir('delay_execution') subdir('dummy_index_am') subdir('dummy_seclabel') +subdir('dummy_sequence_am') subdir('gin') subdir('injection_points') subdir('ldap_password_func') --=20 2.43.0 --OKPlUpZv0l0WYI+j Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0008-Sequence-access-methods-core-documentation.patch" Content-Transfer-Encoding: quoted-printable =46rom fc1f85a968a1f73d1b4dc7ba94b1f021e67bdc06 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Fri, 1 Dec 2023 12:55:56 +0900 Subject: [PATCH v4 8/8] Sequence access methods - core documentation --- doc/src/sgml/config.sgml | 16 +++++ doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/postgres.sgml | 1 + doc/src/sgml/ref/create_access_method.sgml | 15 ++-- doc/src/sgml/ref/create_sequence.sgml | 12 ++++ doc/src/sgml/sequenceam.sgml | 80 ++++++++++++++++++++++ 6 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 doc/src/sgml/sequenceam.sgml diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index d8e1282e12..3d595711c3 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8971,6 +8971,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' W= ITH csv; =20 + + default_sequence_access_method (strin= g) + + default_sequence_access_method configur= ation parameter + + + + + This parameter specifies the default sequence access method to use= when + creating sequences if the CREATE SEQUENCE + command does not explicitly specify an access method. The default = is + local. + + + + default_tablespace (string) diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 38ec362d8f..4e1218aa89 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -95,6 +95,7 @@ + diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index ec9f90e283..6b82f4c7fc 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -256,6 +256,7 @@ break is not needed in a wider output rendering. &geqo; &tableam; &indexam; + &sequenceam; &wal-for-extensions; &indextypes; &storage; diff --git a/doc/src/sgml/ref/create_access_method.sgml b/doc/src/sgml/ref/= create_access_method.sgml index dae43dbaed..3067dc4d4d 100644 --- a/doc/src/sgml/ref/create_access_method.sgml +++ b/doc/src/sgml/ref/create_access_method.sgml @@ -61,8 +61,8 @@ CREATE ACCESS METHOD nam= e This clause specifies the type of access method to define. - Only TABLE and INDEX - are supported at present. + Only TABLE, SEQUENCE and + INDEX are supported at present. @@ -77,12 +77,15 @@ CREATE ACCESS METHOD n= ame declared to take a single argument of type internal, and its return type depends on the type of access method; for TABLE access methods, it must - be table_am_handler and for INDEX - access methods, it must be index_am_handler. + be table_am_handler; for INDEX + access methods, it must be index_am_handler; + for SEQUENCE, it must be + sequence_am_handler; The C-level API that the handler function must implement varies depending on the type of access method. The table access method API - is described in and the index access met= hod - API is described in . + is described in , the index access method + API is described in and the sequence acc= ess + method is described in . diff --git a/doc/src/sgml/ref/create_sequence.sgml b/doc/src/sgml/ref/creat= e_sequence.sgml index 34e9084b5c..d00bdabde0 100644 --- a/doc/src/sgml/ref/create_sequence.sgml +++ b/doc/src/sgml/ref/create_sequence.sgml @@ -27,6 +27,7 @@ CREATE [ { TEMPORARY | TEMP } | UNLOGGED ] SEQUENCE [ IF = NOT EXISTS ] minvalue | N= O MINVALUE ] [ MAXVALUE maxvalue | NO MAXVALUE ] [ START [ WITH ] start = ] [ CACHE cache ] [ [ NO ] C= YCLE ] [ OWNED BY { table_name= =2Ecolumn_name | NONE } ] + [ USING access_method ] =20 @@ -261,6 +262,17 @@ SELECT * FROM name; + + + USING access= _method + + + The USING option specifies which sequence access + method will be used when generating the sequence numbers. The default + is local. + + + =20 diff --git a/doc/src/sgml/sequenceam.sgml b/doc/src/sgml/sequenceam.sgml new file mode 100644 index 0000000000..a96170bfac --- /dev/null +++ b/doc/src/sgml/sequenceam.sgml @@ -0,0 +1,80 @@ + + + + Sequence Access Method Interface Definition + + + Sequence Access Method + + + sequenceam + Sequence Access Method + + + + This chapter explains the interface between the core + PostgreSQL system and sequence acc= ess + methods, which manage the operations around sequences . The = core + system knows little about these access methods beyond what is specified = here, + so it is possible to develop entirely new access method types by writing + add-on code. + + + + Each sequence access method is described by a row in the + pg_am sy= stem + catalog. The pg_am entry specifies a name and a + handler function for the sequence access method. = These + entries can be created and deleted using the + and + SQL commands. + + + + A sequence access method handler function must be declared to accept a s= ingle + argument of type internal and to return the pseudo-type + sequence_am_handler. The argument is a dummy value that si= mply + serves to prevent handler functions from being called directly from SQL = commands. + + The result of the function must be a pointer to a struct of type + SequenceAmRoutine, which contains everything th= at the + core code needs to know to make use of the sequence access method. The r= eturn + value needs to be of server lifetime, which is typically achieved by + defining it as a static const variable in global + scope. The SequenceAmRoutine struct, also calle= d the + access method's API struct, defines the behavior = of + the access method using callbacks. These callbacks are pointers to plain= C + functions and are not visible or callable at the SQL level. All the + callbacks and their behavior is defined in the + SequenceAmRoutine structure (with comments insi= de + the struct defining the requirements for callbacks). Most callbacks have + wrapper functions, which are documented from the point of view of a user + (rather than an implementor) of the sequence access method. For details, + please refer to the + src/include/access/sequenceam.h file. + + + + Currently, the way a sequence access method stores data is fairly + unconstrained, and it is possible to use a predefined + Table Access Method to store sequence + data. + + + + For crash safety, a sequence access method can use + WAL, or a custom + implementation. + If WAL is chosen, either + Generic WAL Records can be used, or= a + Custom WAL Resource Manager can be + implemented. + + + + Any developer of a new sequence access method can ref= er to + the existing local implementation present in + src/backend/access/sequence/local.c for details of + its implementation. + + --=20 2.43.0 --OKPlUpZv0l0WYI+j-- --rTEHaE/0SQFZV9PH Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEG72nH6vTowiyblFKnvQgOdbyQH0FAmYiFowACgkQnvQgOdby QH1Zkw//ReEZzCu/ejW8m8RsIqxAzK6BCYp/SP7iAdxp2groHp/VskY7nE+2juGo Hg9V4WfETY9LuH9l6rO5ZvSBuVZQpKxbZHVKttyeooAEAarYRp4D3zTb6VfBkBgS yYgtKWyWiPO0dAevEww1DBw2u/ouKlDb7J9t3wlOR6tH+igJG0M8Wo7PNs1TytVa 4zQmudstpKQerJ/nT4nwQudLnI+1s3VGQxWjumv49S4khAYtKhQcH5IolHllJI3Y 9H+eFhiLDNlu6KBQdFxl6S5amCdZ/o4rV7X1oWvc+Ha9lHqARC6qAgDWnLb3iaL0 6dzA9SmHfTWGcw3Pw4Y5XpXtwEaR5X5d2w4zJGiK07f4WBsQzBsOB/VKa5Nufr2N gPufhdICHJIyfqY1SV3/VZ87zEZqlJmzwA/MbgyXPXL/r3CHTiV0VQXmz+c5XBIn 9kWbnU6t9jhyZVYKpGvmoG/ik8Oc84vACJDQg2eUj1ifjmQWXsanebmuOR6tJD4W /7081YPqyXT2wWs+nqM4Ov2EARNRghOjR5zbSL7T5uFAAsMrUyQGnhFOp6vSNi6X hEmugnK2YsGA7387B5lbQ0SZy9n/AsbdqJ0E2hBh3DGDRPhzvlp6vXAhPH5Zwvzj kBF7gbH9I+Llx3F5d3BmXhHP/uIiYlFW0ESY/ZcH2EPlnptdwUo= =Abwb -----END PGP SIGNATURE----- --rTEHaE/0SQFZV9PH--