On Wed, Jul 17, 2024 at 11:58:21PM -0400, Tom Lane wrote: > ... okay, I lied, I looked at the patch. Why are you testing > > + if (pg_class_aclcheck(relid, GetUserId(), ACL_SELECT | ACL_USAGE) == > ACLCHECK_OK && > > ? This is a substitute for a SELECT from the sequence and it seems > like it ought to demand exactly the same privilege as SELECT. > (If you want to get more technical, USAGE allows nextval() which > gives strictly less information than what this exposes; that's why > we're here after all.) So there is a difference in the privilege > levels, which is another reason for not combining this with > pg_sequence_last_value.
Oh, that's a good point. I wrongly assumed the privilege checks would be the same as pg_sequence_last_value(). I fixed this in v5. I also polished the rest of the patches a bit. Among other things, I created an enum for the sequence data types to avoid the hacky strncpy() stuff, which was causing weird CI failures [0]. [0] https://cirrus-ci.com/task/4614801962303488 -- nathan
>From e98ff5c9a5e8962445de0d5ab68ce50ed40c121b Mon Sep 17 00:00:00 2001 From: Nathan Bossart <nat...@postgresql.org> Date: Mon, 15 Jul 2024 13:13:05 -0500 Subject: [PATCH v5 1/4] parse sequence information --- src/bin/pg_dump/pg_dump.c | 102 ++++++++++++++++++------------- src/tools/pgindent/typedefs.list | 1 + 2 files changed, 59 insertions(+), 44 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index b8b1888bd3..afafd641fe 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -104,6 +104,23 @@ typedef struct RelFileNumber toast_index_relfilenumber; /* toast table index filenode */ } BinaryUpgradeClassOidItem; +typedef enum SeqType +{ + SEQTYPE_SMALLINT, + SEQTYPE_INTEGER, + SEQTYPE_BIGINT, +} SeqType; + +const char *const SeqTypeNames[] = +{ + [SEQTYPE_SMALLINT] = "smallint", + [SEQTYPE_INTEGER] = "integer", + [SEQTYPE_BIGINT] = "bigint", +}; + +StaticAssertDecl(lengthof(SeqTypeNames) == (SEQTYPE_BIGINT + 1), + "array length mismatch"); + typedef enum OidOptions { zeroIsError = 1, @@ -17189,6 +17206,18 @@ dumpTableConstraintComment(Archive *fout, const ConstraintInfo *coninfo) free(qtabname); } +static inline SeqType +parse_sequence_type(const char *name) +{ + for (int i = 0; i < lengthof(SeqTypeNames); i++) + { + if (strcmp(SeqTypeNames[i], name) == 0) + return (SeqType) i; + } + + pg_fatal("unrecognized sequence type: %s", name); +} + /* * dumpSequence * write the declaration (not data) of one user-defined sequence @@ -17198,18 +17227,16 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) { DumpOptions *dopt = fout->dopt; PGresult *res; - char *startv, - *incby, - *maxv, - *minv, - *cache, - *seqtype; + SeqType seqtype; bool cycled; bool is_ascending; int64 default_minv, - default_maxv; - char bufm[32], - bufx[32]; + default_maxv, + minv, + maxv, + startv, + incby, + cache; PQExpBuffer query = createPQExpBuffer(); PQExpBuffer delqry = createPQExpBuffer(); char *qseqname; @@ -17251,50 +17278,39 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) PQntuples(res)), tbinfo->dobj.name, PQntuples(res)); - seqtype = PQgetvalue(res, 0, 0); - startv = PQgetvalue(res, 0, 1); - incby = PQgetvalue(res, 0, 2); - maxv = PQgetvalue(res, 0, 3); - minv = PQgetvalue(res, 0, 4); - cache = PQgetvalue(res, 0, 5); + seqtype = parse_sequence_type(PQgetvalue(res, 0, 0)); + startv = strtoi64(PQgetvalue(res, 0, 1), NULL, 10); + incby = strtoi64(PQgetvalue(res, 0, 2), NULL, 10); + maxv = strtoi64(PQgetvalue(res, 0, 3), NULL, 10); + minv = strtoi64(PQgetvalue(res, 0, 4), NULL, 10); + cache = strtoi64(PQgetvalue(res, 0, 5), NULL, 10); cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); + PQclear(res); + /* Calculate default limits for a sequence of this type */ - is_ascending = (incby[0] != '-'); - if (strcmp(seqtype, "smallint") == 0) + is_ascending = (incby >= 0); + if (seqtype == SEQTYPE_SMALLINT) { default_minv = is_ascending ? 1 : PG_INT16_MIN; default_maxv = is_ascending ? PG_INT16_MAX : -1; } - else if (strcmp(seqtype, "integer") == 0) + else if (seqtype == SEQTYPE_INTEGER) { default_minv = is_ascending ? 1 : PG_INT32_MIN; default_maxv = is_ascending ? PG_INT32_MAX : -1; } - else if (strcmp(seqtype, "bigint") == 0) + else if (seqtype == SEQTYPE_BIGINT) { default_minv = is_ascending ? 1 : PG_INT64_MIN; default_maxv = is_ascending ? PG_INT64_MAX : -1; } else { - pg_fatal("unrecognized sequence type: %s", seqtype); + pg_fatal("unrecognized sequence type: %d", seqtype); default_minv = default_maxv = 0; /* keep compiler quiet */ } - /* - * 64-bit strtol() isn't very portable, so convert the limits to strings - * and compare that way. - */ - snprintf(bufm, sizeof(bufm), INT64_FORMAT, default_minv); - snprintf(bufx, sizeof(bufx), INT64_FORMAT, default_maxv); - - /* Don't print minv/maxv if they match the respective default limit */ - if (strcmp(minv, bufm) == 0) - minv = NULL; - if (strcmp(maxv, bufx) == 0) - maxv = NULL; - /* * Identity sequences are not to be dropped separately. */ @@ -17342,26 +17358,26 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) "UNLOGGED " : "", fmtQualifiedDumpable(tbinfo)); - if (strcmp(seqtype, "bigint") != 0) - appendPQExpBuffer(query, " AS %s\n", seqtype); + if (seqtype != SEQTYPE_BIGINT) + appendPQExpBuffer(query, " AS %s\n", SeqTypeNames[seqtype]); } - appendPQExpBuffer(query, " START WITH %s\n", startv); + appendPQExpBuffer(query, " START WITH " INT64_FORMAT "\n", startv); - appendPQExpBuffer(query, " INCREMENT BY %s\n", incby); + appendPQExpBuffer(query, " INCREMENT BY " INT64_FORMAT "\n", incby); - if (minv) - appendPQExpBuffer(query, " MINVALUE %s\n", minv); + if (minv != default_minv) + appendPQExpBuffer(query, " MINVALUE " INT64_FORMAT "\n", minv); else appendPQExpBufferStr(query, " NO MINVALUE\n"); - if (maxv) - appendPQExpBuffer(query, " MAXVALUE %s\n", maxv); + if (maxv != default_maxv) + appendPQExpBuffer(query, " MAXVALUE " INT64_FORMAT "\n", maxv); else appendPQExpBufferStr(query, " NO MAXVALUE\n"); appendPQExpBuffer(query, - " CACHE %s%s", + " CACHE " INT64_FORMAT "%s", cache, (cycled ? "\n CYCLE" : "")); if (tbinfo->is_identity_sequence) @@ -17448,8 +17464,6 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); - PQclear(res); - destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); free(qseqname); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index b4d7f9217c..974e21155a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2572,6 +2572,7 @@ SeqScan SeqScanState SeqTable SeqTableData +SeqType SerCommitSeqNo SerialControl SerialIOData -- 2.39.3 (Apple Git-146)
>From 6f0ce8705acf916163f1272bd9e546e683faaa11 Mon Sep 17 00:00:00 2001 From: Nathan Bossart <nat...@postgresql.org> Date: Tue, 9 Jul 2024 14:06:23 -0500 Subject: [PATCH v5 2/4] cache sequence information --- src/bin/pg_dump/pg_dump.c | 175 ++++++++++++++++++++++--------- src/tools/pgindent/typedefs.list | 1 + 2 files changed, 129 insertions(+), 47 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index afafd641fe..9d1707623b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -121,6 +121,18 @@ const char *const SeqTypeNames[] = StaticAssertDecl(lengthof(SeqTypeNames) == (SEQTYPE_BIGINT + 1), "array length mismatch"); +typedef struct +{ + Oid oid; /* sequence OID */ + SeqType seqtype; /* data type of sequence */ + bool cycled; /* whether sequence cycles */ + int64 minv; /* minimum value */ + int64 maxv; /* maximum value */ + int64 startv; /* start value */ + int64 incby; /* increment value */ + int64 cache; /* cache size */ +} SequenceItem; + typedef enum OidOptions { zeroIsError = 1, @@ -190,6 +202,10 @@ static int nseclabels = 0; static BinaryUpgradeClassOidItem *binaryUpgradeClassOids = NULL; static int nbinaryUpgradeClassOids = 0; +/* sorted table of sequences */ +static SequenceItem *sequences = NULL; +static int nsequences = 0; + /* * The default number of rows per INSERT when * --inserts is specified without --rows-per-insert @@ -287,6 +303,7 @@ static void dumpTable(Archive *fout, const TableInfo *tbinfo); static void dumpTableSchema(Archive *fout, const TableInfo *tbinfo); static void dumpTableAttach(Archive *fout, const TableAttachInfo *attachinfo); static void dumpAttrDef(Archive *fout, const AttrDefInfo *adinfo); +static void collectSequences(Archive *fout); static void dumpSequence(Archive *fout, const TableInfo *tbinfo); static void dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo); static void dumpIndex(Archive *fout, const IndxInfo *indxinfo); @@ -1009,6 +1026,9 @@ main(int argc, char **argv) if (dopt.binary_upgrade) collectBinaryUpgradeClassOids(fout); + /* Collect sequence information. */ + collectSequences(fout); + /* Lastly, create dummy objects to represent the section boundaries */ boundaryObjs = createBoundaryObjects(); @@ -17218,6 +17238,65 @@ parse_sequence_type(const char *name) pg_fatal("unrecognized sequence type: %s", name); } +/* + * bsearch() comparator for SequenceItem + */ +static int +SequenceItemCmp(const void *p1, const void *p2) +{ + SequenceItem v1 = *((const SequenceItem *) p1); + SequenceItem v2 = *((const SequenceItem *) p2); + + return pg_cmp_u32(v1.oid, v2.oid); +} + +/* + * collectSequences + * + * Construct a table of sequence information. This table is sorted by OID for + * speed in lookup. + */ +static void +collectSequences(Archive *fout) +{ + PGresult *res; + const char *query; + + /* + * Before Postgres 10, sequence metadata is in the sequence itself. We + * could likely make use of the sorted table with some extra effort, but + * for now it seems unlikely to be worth it. + */ + if (fout->remoteVersion < 100000) + return; + else + query = "SELECT seqrelid, format_type(seqtypid, NULL), " + "seqstart, seqincrement, " + "seqmax, seqmin, " + "seqcache, seqcycle " + "FROM pg_catalog.pg_sequence " + "ORDER BY seqrelid"; + + res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); + + nsequences = PQntuples(res); + sequences = (SequenceItem *) pg_malloc(nsequences * sizeof(SequenceItem)); + + for (int i = 0; i < nsequences; i++) + { + sequences[i].oid = atooid(PQgetvalue(res, i, 0)); + sequences[i].seqtype = parse_sequence_type(PQgetvalue(res, i, 1)); + sequences[i].startv = strtoi64(PQgetvalue(res, i, 2), NULL, 10); + sequences[i].incby = strtoi64(PQgetvalue(res, i, 3), NULL, 10); + sequences[i].maxv = strtoi64(PQgetvalue(res, i, 4), NULL, 10); + sequences[i].minv = strtoi64(PQgetvalue(res, i, 5), NULL, 10); + sequences[i].cache = strtoi64(PQgetvalue(res, i, 6), NULL, 10); + sequences[i].cycled = (strcmp(PQgetvalue(res, i, 7), "t") == 0); + } + + PQclear(res); +} + /* * dumpSequence * write the declaration (not data) of one user-defined sequence @@ -17226,17 +17305,10 @@ static void dumpSequence(Archive *fout, const TableInfo *tbinfo) { DumpOptions *dopt = fout->dopt; - PGresult *res; - SeqType seqtype; - bool cycled; + SequenceItem *seq; bool is_ascending; int64 default_minv, - default_maxv, - minv, - maxv, - startv, - incby, - cache; + default_maxv; PQExpBuffer query = createPQExpBuffer(); PQExpBuffer delqry = createPQExpBuffer(); char *qseqname; @@ -17244,19 +17316,25 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) qseqname = pg_strdup(fmtId(tbinfo->dobj.name)); + /* + * For versions >= 10, the sequence information is gathered in a sorted + * table before any calls to dumpSequence(). See collectSequences() for + * more information. + */ if (fout->remoteVersion >= 100000) { - appendPQExpBuffer(query, - "SELECT format_type(seqtypid, NULL), " - "seqstart, seqincrement, " - "seqmax, seqmin, " - "seqcache, seqcycle " - "FROM pg_catalog.pg_sequence " - "WHERE seqrelid = '%u'::oid", - tbinfo->dobj.catId.oid); + SequenceItem key = {0}; + + Assert(sequences); + + key.oid = tbinfo->dobj.catId.oid; + seq = bsearch(&key, sequences, nsequences, + sizeof(SequenceItem), SequenceItemCmp); } else { + PGresult *res; + /* * Before PostgreSQL 10, sequence metadata is in the sequence itself. * @@ -17268,46 +17346,47 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) "start_value, increment_by, max_value, min_value, " "cache_value, is_cycled FROM %s", fmtQualifiedDumpable(tbinfo)); - } - - res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); - if (PQntuples(res) != 1) - pg_fatal(ngettext("query to get data of sequence \"%s\" returned %d row (expected 1)", - "query to get data of sequence \"%s\" returned %d rows (expected 1)", - PQntuples(res)), - tbinfo->dobj.name, PQntuples(res)); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); - seqtype = parse_sequence_type(PQgetvalue(res, 0, 0)); - startv = strtoi64(PQgetvalue(res, 0, 1), NULL, 10); - incby = strtoi64(PQgetvalue(res, 0, 2), NULL, 10); - maxv = strtoi64(PQgetvalue(res, 0, 3), NULL, 10); - minv = strtoi64(PQgetvalue(res, 0, 4), NULL, 10); - cache = strtoi64(PQgetvalue(res, 0, 5), NULL, 10); - cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); + if (PQntuples(res) != 1) + pg_fatal(ngettext("query to get data of sequence \"%s\" returned %d row (expected 1)", + "query to get data of sequence \"%s\" returned %d rows (expected 1)", + PQntuples(res)), + tbinfo->dobj.name, PQntuples(res)); + + seq = pg_malloc0(sizeof(SequenceItem)); + seq->seqtype = parse_sequence_type(PQgetvalue(res, 0, 0)); + seq->startv = strtoi64(PQgetvalue(res, 0, 1), NULL, 10); + seq->incby = strtoi64(PQgetvalue(res, 0, 2), NULL, 10); + seq->maxv = strtoi64(PQgetvalue(res, 0, 3), NULL, 10); + seq->minv = strtoi64(PQgetvalue(res, 0, 4), NULL, 10); + seq->cache = strtoi64(PQgetvalue(res, 0, 5), NULL, 10); + seq->cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); - PQclear(res); + PQclear(res); + } /* Calculate default limits for a sequence of this type */ - is_ascending = (incby >= 0); - if (seqtype == SEQTYPE_SMALLINT) + is_ascending = (seq->incby >= 0); + if (seq->seqtype == SEQTYPE_SMALLINT) { default_minv = is_ascending ? 1 : PG_INT16_MIN; default_maxv = is_ascending ? PG_INT16_MAX : -1; } - else if (seqtype == SEQTYPE_INTEGER) + else if (seq->seqtype == SEQTYPE_INTEGER) { default_minv = is_ascending ? 1 : PG_INT32_MIN; default_maxv = is_ascending ? PG_INT32_MAX : -1; } - else if (seqtype == SEQTYPE_BIGINT) + else if (seq->seqtype == SEQTYPE_BIGINT) { default_minv = is_ascending ? 1 : PG_INT64_MIN; default_maxv = is_ascending ? PG_INT64_MAX : -1; } else { - pg_fatal("unrecognized sequence type: %d", seqtype); + pg_fatal("unrecognized sequence type: %d", seq->seqtype); default_minv = default_maxv = 0; /* keep compiler quiet */ } @@ -17358,27 +17437,27 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) "UNLOGGED " : "", fmtQualifiedDumpable(tbinfo)); - if (seqtype != SEQTYPE_BIGINT) - appendPQExpBuffer(query, " AS %s\n", SeqTypeNames[seqtype]); + if (seq->seqtype != SEQTYPE_BIGINT) + appendPQExpBuffer(query, " AS %s\n", SeqTypeNames[seq->seqtype]); } - appendPQExpBuffer(query, " START WITH " INT64_FORMAT "\n", startv); + appendPQExpBuffer(query, " START WITH " INT64_FORMAT "\n", seq->startv); - appendPQExpBuffer(query, " INCREMENT BY " INT64_FORMAT "\n", incby); + appendPQExpBuffer(query, " INCREMENT BY " INT64_FORMAT "\n", seq->incby); - if (minv != default_minv) - appendPQExpBuffer(query, " MINVALUE " INT64_FORMAT "\n", minv); + if (seq->minv != default_minv) + appendPQExpBuffer(query, " MINVALUE " INT64_FORMAT "\n", seq->minv); else appendPQExpBufferStr(query, " NO MINVALUE\n"); - if (maxv != default_maxv) - appendPQExpBuffer(query, " MAXVALUE " INT64_FORMAT "\n", maxv); + if (seq->maxv != default_maxv) + appendPQExpBuffer(query, " MAXVALUE " INT64_FORMAT "\n", seq->maxv); else appendPQExpBufferStr(query, " NO MAXVALUE\n"); appendPQExpBuffer(query, " CACHE " INT64_FORMAT "%s", - cache, (cycled ? "\n CYCLE" : "")); + seq->cache, (seq->cycled ? "\n CYCLE" : "")); if (tbinfo->is_identity_sequence) { @@ -17464,6 +17543,8 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); + if (fout->remoteVersion < 100000) + pg_free(seq); destroyPQExpBuffer(query); destroyPQExpBuffer(delqry); free(qseqname); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 974e21155a..e1a46b5bf5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2573,6 +2573,7 @@ SeqScanState SeqTable SeqTableData SeqType +SequenceItem SerCommitSeqNo SerialControl SerialIOData -- 2.39.3 (Apple Git-146)
>From c74861298d44ab37f6b8bf1dd864614b8131dc20 Mon Sep 17 00:00:00 2001 From: Nathan Bossart <nat...@postgresql.org> Date: Wed, 17 Jul 2024 17:09:03 -0500 Subject: [PATCH v5 3/4] introduce pg_sequence_read_tuple XXX: NEEDS CATVERSION BUMP --- src/backend/commands/sequence.c | 62 ++++++++++++++++++++++++++ src/include/catalog/pg_proc.dat | 6 +++ src/test/regress/expected/sequence.out | 7 +++ src/test/regress/sql/sequence.sql | 3 ++ 4 files changed, 78 insertions(+) diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 9f28d40466..364097c457 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1773,6 +1773,68 @@ pg_sequence_parameters(PG_FUNCTION_ARGS) return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull)); } + +/* + * Return the sequence tuple. + * + * This is primarily intended for use by pg_dump to gather sequence data + * without needing to individually query each sequence relation. + */ +Datum +pg_sequence_read_tuple(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + SeqTable elm; + Relation seqrel; + Datum values[SEQ_COL_LASTCOL]; + bool isnull[SEQ_COL_LASTCOL]; + TupleDesc resultTupleDesc; + HeapTuple resultHeapTuple; + Datum result; + + resultTupleDesc = CreateTemplateTupleDesc(SEQ_COL_LASTCOL); + TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "last_value", + INT8OID, -1, 0); + TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "log_cnt", + INT8OID, -1, 0); + TupleDescInitEntry(resultTupleDesc, (AttrNumber) 3, "is_called", + BOOLOID, -1, 0); + resultTupleDesc = BlessTupleDesc(resultTupleDesc); + + init_sequence(relid, &elm, &seqrel); + + /* + * Return all NULLs for sequences for which we lack privileges, other + * sessions' temporary sequences, and unlogged sequences on standbys. + */ + if (pg_class_aclcheck(relid, GetUserId(), ACL_SELECT) == ACLCHECK_OK && + !RELATION_IS_OTHER_TEMP(seqrel) && + (RelationIsPermanent(seqrel) || !RecoveryInProgress())) + { + Buffer buf; + HeapTupleData seqtuple; + Form_pg_sequence_data seq; + + seq = read_seq_tuple(seqrel, &buf, &seqtuple); + + memset(isnull, false, sizeof(isnull)); + values[0] = Int64GetDatum(seq->last_value); + values[1] = Int64GetDatum(seq->log_cnt); + values[2] = BoolGetDatum(seq->is_called); + + UnlockReleaseBuffer(buf); + } + else + memset(isnull, true, sizeof(isnull)); + + sequence_close(seqrel, NoLock); + + resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull); + result = HeapTupleGetDatum(resultHeapTuple); + PG_RETURN_DATUM(result); +} + + /* * Return the last value from the sequence * diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 73d9cf8582..a16aec302e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -3329,6 +3329,12 @@ proname => 'pg_sequence_last_value', provolatile => 'v', proparallel => 'u', prorettype => 'int8', proargtypes => 'regclass', prosrc => 'pg_sequence_last_value' }, +{ oid => '9876', descr => 'return sequence tuple, for use by pg_dump', + proname => 'pg_sequence_read_tuple', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proargtypes => 'regclass', + proallargtypes => '{regclass,int8,int8,bool}', proargmodes => '{i,o,o,o}', + proargnames => '{sequence_oid,last_value,log_cnt,is_called}', + prosrc => 'pg_sequence_read_tuple' }, { oid => '275', descr => 'return the next oid for a system table', proname => 'pg_nextoid', provolatile => 'v', proparallel => 'u', diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out index 2b47b7796b..e749c4574e 100644 --- a/src/test/regress/expected/sequence.out +++ b/src/test/regress/expected/sequence.out @@ -839,4 +839,11 @@ SELECT nextval('test_seq1'); 3 (1 row) +-- pg_sequence_read_tuple +SELECT * FROM pg_sequence_read_tuple('test_seq1'); + last_value | log_cnt | is_called +------------+---------+----------- + 10 | 32 | t +(1 row) + DROP SEQUENCE test_seq1; diff --git a/src/test/regress/sql/sequence.sql b/src/test/regress/sql/sequence.sql index 674f5f1f66..ea447938ae 100644 --- a/src/test/regress/sql/sequence.sql +++ b/src/test/regress/sql/sequence.sql @@ -413,4 +413,7 @@ SELECT nextval('test_seq1'); SELECT nextval('test_seq1'); SELECT nextval('test_seq1'); +-- pg_sequence_read_tuple +SELECT * FROM pg_sequence_read_tuple('test_seq1'); + DROP SEQUENCE test_seq1; -- 2.39.3 (Apple Git-146)
>From 66c8711d0a14db4e418bbf136437568f6b48ce40 Mon Sep 17 00:00:00 2001 From: Nathan Bossart <nat...@postgresql.org> Date: Wed, 17 Jul 2024 22:13:08 -0500 Subject: [PATCH v5 4/4] cache sequence data --- src/bin/pg_dump/pg_dump.c | 81 ++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 9d1707623b..2c5c614abd 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -131,6 +131,8 @@ typedef struct int64 startv; /* start value */ int64 incby; /* increment value */ int64 cache; /* cache size */ + int64 last_value; /* last value of sequence */ + bool is_called; /* whether nextval advances before returning */ } SequenceItem; typedef enum OidOptions @@ -17266,16 +17268,30 @@ collectSequences(Archive *fout) * Before Postgres 10, sequence metadata is in the sequence itself. We * could likely make use of the sorted table with some extra effort, but * for now it seems unlikely to be worth it. + * + * Since version 18, we can gather the sequence data in this query with + * pg_sequence_read_tuple(), but we only do so for non-schema-only dumps. */ if (fout->remoteVersion < 100000) return; - else + else if (fout->remoteVersion < 180000 || + (fout->dopt->schemaOnly && !fout->dopt->sequence_data)) query = "SELECT seqrelid, format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " - "seqcache, seqcycle " + "seqcache, seqcycle, " + "NULL, 'f' " "FROM pg_catalog.pg_sequence " "ORDER BY seqrelid"; + else + query = "SELECT seqrelid, format_type(seqtypid, NULL), " + "seqstart, seqincrement, " + "seqmax, seqmin, " + "seqcache, seqcycle, " + "last_value, is_called " + "FROM pg_catalog.pg_sequence, " + "pg_sequence_read_tuple(seqrelid) " + "ORDER BY seqrelid;"; res = ExecuteSqlQuery(fout, query, PGRES_TUPLES_OK); @@ -17292,6 +17308,8 @@ collectSequences(Archive *fout) sequences[i].minv = strtoi64(PQgetvalue(res, i, 5), NULL, 10); sequences[i].cache = strtoi64(PQgetvalue(res, i, 6), NULL, 10); sequences[i].cycled = (strcmp(PQgetvalue(res, i, 7), "t") == 0); + sequences[i].last_value = strtoi64(PQgetvalue(res, i, 8), NULL, 10); + sequences[i].is_called = (strcmp(PQgetvalue(res, i, 9), "t") == 0); } PQclear(res); @@ -17558,30 +17576,59 @@ static void dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo) { TableInfo *tbinfo = tdinfo->tdtable; - PGresult *res; - char *last; + int64 last; bool called; PQExpBuffer query = createPQExpBuffer(); - appendPQExpBuffer(query, - "SELECT last_value, is_called FROM %s", - fmtQualifiedDumpable(tbinfo)); + /* + * For versions >= 18, the sequence information is gathered in the sorted + * array before any calls to dumpSequenceData(). See collectSequences() + * for more information. + * + * For older versions, we have to query the sequence relations + * individually. + */ + if (fout->remoteVersion < 180000) + { + PGresult *res; - res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + appendPQExpBuffer(query, + "SELECT last_value, is_called FROM %s", + fmtQualifiedDumpable(tbinfo)); - if (PQntuples(res) != 1) - pg_fatal(ngettext("query to get data of sequence \"%s\" returned %d row (expected 1)", - "query to get data of sequence \"%s\" returned %d rows (expected 1)", - PQntuples(res)), - tbinfo->dobj.name, PQntuples(res)); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); - last = PQgetvalue(res, 0, 0); - called = (strcmp(PQgetvalue(res, 0, 1), "t") == 0); + if (PQntuples(res) != 1) + pg_fatal(ngettext("query to get data of sequence \"%s\" returned %d row (expected 1)", + "query to get data of sequence \"%s\" returned %d rows (expected 1)", + PQntuples(res)), + tbinfo->dobj.name, PQntuples(res)); + + last = strtoi64(PQgetvalue(res, 0, 0), NULL, 10); + called = (strcmp(PQgetvalue(res, 0, 1), "t") == 0); + + PQclear(res); + } + else + { + SequenceItem key = {0}; + SequenceItem *entry; + + Assert(sequences); + Assert(tbinfo->dobj.catId.oid); + + key.oid = tbinfo->dobj.catId.oid; + entry = bsearch(&key, sequences, nsequences, + sizeof(SequenceItem), SequenceItemCmp); + + last = entry->last_value; + called = entry->is_called; + } resetPQExpBuffer(query); appendPQExpBufferStr(query, "SELECT pg_catalog.setval("); appendStringLiteralAH(query, fmtQualifiedDumpable(tbinfo), fout); - appendPQExpBuffer(query, ", %s, %s);\n", + appendPQExpBuffer(query, ", " INT64_FORMAT ", %s);\n", last, (called ? "true" : "false")); if (tdinfo->dobj.dump & DUMP_COMPONENT_DATA) @@ -17595,8 +17642,6 @@ dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo) .deps = &(tbinfo->dobj.dumpId), .nDeps = 1)); - PQclear(res); - destroyPQExpBuffer(query); } -- 2.39.3 (Apple Git-146)